From 13b9c2a451481868f3bc5eedbacea385bf567b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 10:14:42 +0800 Subject: [PATCH 01/17] =?UTF-8?q?refactor(Trade=5FSelf):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BA=92=E6=8D=A2=E4=BA=A4=E6=98=93=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=9D=83=E9=99=90=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 提取互换事件检查条件为独立变量 hasSwapDealEvent - 添加对平仓、互换、自动互换事件类型的检查 - 将复杂条件判断重构为更清晰的逻辑结构 - 保持原有业务逻辑不变的情况下提高代码可读性 --- YLErpDAL/Model/Trade_Self.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/YLErpDAL/Model/Trade_Self.cs b/YLErpDAL/Model/Trade_Self.cs index 237a7b44..8f2e69a6 100644 --- a/YLErpDAL/Model/Trade_Self.cs +++ b/YLErpDAL/Model/Trade_Self.cs @@ -199,8 +199,14 @@ namespace YLErp.DBModels } public bool SwapCanEdit() { + var hasSwapDealEvent = swap_Events.Any(x => + !x.Invalid + && (x.EventType == (int)SwapEventTypeEnum.平仓 + || x.EventType == (int)SwapEventTypeEnum.互换 + || x.EventType == (int)SwapEventTypeEnum.自动互换)); + //确认成交 - return !(ConsTrade.TradeStatusAfterAndWithOutConfirmed.Contains(TradeStatus) || ConsTrade.审批中.Equals(TradeStatus) || UnWindDate.HasValue || (!PS.Config.TradeElement.IsAmendableAfterConfirm && TradeDate < valuedateBLL.ValueDate && ConsTrade.确认成交.Equals(TradeStatus))); + return !(ConsTrade.TradeStatusAfterAndWithOutConfirmed.Contains(TradeStatus) || ConsTrade.审批中.Equals(TradeStatus) || UnWindDate.HasValue || hasSwapDealEvent || (!PS.Config.TradeElement.IsAmendableAfterConfirm && TradeDate < valuedateBLL.ValueDate && ConsTrade.确认成交.Equals(TradeStatus))); } public bool CanDelete() { From 7d523112205b58437d820417e35c4b9055ee342a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 13:32:02 +0800 Subject: [PATCH 02/17] =?UTF-8?q?fix(swap-trade):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E5=B1=95=E6=9C=9F=E5=B9=B3=E4=BB=93=E9=A1=B5=E9=9D=A2=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E6=98=BE=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将浮仓数量字段从 floatPosition.Quantity 更新为 deal.CloseQty - 确保页面显示正确的平仓数量信息 --- YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml b/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml index 029907c6..9eb4bd3b 100644 --- a/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml +++ b/YLErpWeb/Views/SwapTrade2/SwapUnwind.cshtml @@ -204,7 +204,7 @@ - {{floatPosition.Quantity}} + {{deal.CloseQty}}
我方{{floatPosition.PayDirection==1?"支付":"收取"}}交易费用
From 3534ebb342ae670777f2666d95dc4f8803192891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 14:07:19 +0800 Subject: [PATCH 03/17] =?UTF-8?q?fix(trade):=20=E4=BF=AE=E6=AD=A3=E5=AF=B9?= =?UTF-8?q?=E5=AE=A2=E4=BA=A4=E6=98=93=E5=88=A4=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了GuolianContractNoGenerator中对客交易的判断条件 - 将判断标准从"乙方=对客"改为"甲方=对客" - 更新了相关注释以反映新的业务规则 --- .../DocGenerateModule/GuolianContractNoGenerator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs b/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs index 4ca0b788..67901f31 100644 --- a/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs +++ b/YLErpDAL/Modules/TradeModule/DocGenerateModule/GuolianContractNoGenerator.cs @@ -8,9 +8,9 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule /// /// 国贸交易确认书编号生成器 /// 规则: - /// - 对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔对客交易(数字递增)}-{标的代码},从01开始 + /// - 对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-FICC-{当日第N笔对客交易(数字递增)}-{标的代码},从01开始 /// 例:GLMS-IS-20260424-01-250210IB - /// - 非对客交易(OpponentRole为"甲方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-{当日第N笔非对客交易使用字母递增}-{标的代码},从A开始 + /// - 非对客交易(OpponentRole为"乙方"):GLMS-{对手方代码}-{成交日期(yyyyMMdd)}-FICC-{当日第N笔非对客交易使用字母递增}-{标的代码},从A开始 /// 例:GLMS-IS-20260424-A-250210IB /// public class GuolianContractNoGenerator @@ -29,8 +29,8 @@ namespace YLErp.Modules.TradeModule.DocGenerateModule trade trade, string clientCode) { - // 根据trade的OpponentRole判断是否为对客交易(乙方=对客) - var isClientTrade = trade.OpponentRole == "乙方"; + // 根据trade的OpponentRole判断是否为对客交易(甲方=对客)。glms客户规定甲方=对客 + var isClientTrade = trade.OpponentRole == "甲方"; var underlyingCode = (trade.UnderlyingCode ?? "").Replace(".", ""); // 去掉标的代码中的点号 var tradeDate = trade.TradeDate ?? DateTime.MinValue; var tradeDateStr = tradeDate.ToString("yyyyMMdd"); From b2dce0e4f7b04e94441d0243e8959b7b12844903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 14:28:47 +0800 Subject: [PATCH 04/17] =?UTF-8?q?fix(swap):=20=E4=BF=AE=E5=A4=8D=E4=BA=92?= =?UTF-8?q?=E6=8D=A2=E4=BA=A4=E6=98=93=E6=B5=81=E7=A8=8B=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=A1=AB=E5=85=85=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加SwapTradeNo、EventType和EventReason字段到流程事件 - 修正EventType枚举类型从SwapEventTypeEnum改为SwapFlowEventTypeEnum - 在SaveSwapDeal方法中添加交易信息验证逻辑 - 提取FillSwapFlowEvent方法统一处理流程事件数据填充 - 优化数据库保存操作确保数据一致性 --- .../Modules/SwapModule/SwapDealService.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/YLErpDAL/Modules/SwapModule/SwapDealService.cs b/YLErpDAL/Modules/SwapModule/SwapDealService.cs index c79a30a5..fcffad1c 100644 --- a/YLErpDAL/Modules/SwapModule/SwapDealService.cs +++ b/YLErpDAL/Modules/SwapModule/SwapDealService.cs @@ -254,6 +254,9 @@ namespace YLErp.Modules.SwapModule unwindData.PayDate = QdpCalendarHelper.GetNonHoliday(unwindData.UnwindDate.Value.AddDays(td.trade_extend.ExtendObj.SettlementRules)); floatEvent.PayDate = unwindData.PayDate; floatEvent.SwapTradeId = tradeId; + floatEvent.SwapTradeNo = td.TradeNumber; + floatEvent.EventType = (int)SwapFlowEventTypeEnum.互换; + floatEvent.EventReason = "交易"; unwindData.SwapTradeId = tradeId; unwindData.StructureType = td.StructureType; unwindData.NotionalValue = Convert.ToDecimal(td.OriginalStockEqvNotional ?? 0); @@ -265,8 +268,6 @@ namespace YLErp.Modules.SwapModule unwindData.CloseNotionalValue = unwindData.PosiNotionalValue; if (position != null) { - floatEvent.EventType = (int)SwapEventTypeEnum.互换; - floatEvent.EventReason = "交易"; floatEvent.PositionId = position.PositionId; floatEvent.DividendIn = 0; floatEvent.UnderlyingCode = position.UnderlyingCode; @@ -1619,6 +1620,11 @@ namespace YLErp.Modules.SwapModule /// private long SaveSwapDeal(UnwindData unwindData, int eventType, int clientCashId, string eventResason = "", bool approve = false) { + var td = DbContext.trade.Find(unwindData.SwapTradeId); + if (td == null) + { + throw new ServiceException("未找到交易信息"); + } var flowList = new List(unwindData.FlowEvents); unwindData.FlowEvents.Clear(); string data = JsonConvert.SerializeObject(unwindData); @@ -1633,7 +1639,7 @@ namespace YLErp.Modules.SwapModule item.PayDate = unwindData.PayDate; item.UnwindDate = unwindData.UnwindDate; item.EventDate = unwindData.ValueDate; - item.EventId = swapEvent.id; + FillSwapFlowEvent(item, unwindData, td, eventType, swapEvent.id); DbContext.swap_flow_event.Add(item); } if (!approve) @@ -1641,8 +1647,25 @@ namespace YLErp.Modules.SwapModule UpdateInitalPosition(flowList, unwindData, eventType); } DbContext.SaveChanges(); + var savedFlowEvents = DbContext.swap_flow_event.Where(x => x.EventId == swapEvent.id).ToList(); + foreach (var item in savedFlowEvents) + { + FillSwapFlowEvent(item, unwindData, td, eventType, swapEvent.id); + } + DbContext.SaveChanges(); return swapEvent.id; } + + private void FillSwapFlowEvent(swap_flow_event item, UnwindData unwindData, trade td, int eventType, long eventId) + { + item.EventId = eventId; + item.SwapTradeId = unwindData.SwapTradeId; + item.SwapTradeNo = td.TradeNumber; + item.EventType = eventType; + item.EventReason = string.IsNullOrWhiteSpace(item.EventReason) ? "交易" : item.EventReason; + UpdateDbOption(item); + } + /// /// 计算平仓数据 /// From 1a15918f3de9e949d9d5742d90c4ebc9f9cdb3cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:01:38 +0800 Subject: [PATCH 05/17] =?UTF-8?q?Revert=20"=E4=BF=AE=E6=94=B9=E9=A3=8E?= =?UTF-8?q?=E6=8E=A7=E8=A7=84=E5=88=99=E6=93=8D=E4=BD=9C=E6=9D=83=E9=99=90?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 29ac61e2d51aeb5a45924cbae8bfdae0fff59142. --- .../Ver-5.6.0/seed_menu_risk_alert.sql | 36 +---------- YLErpWeb/Common/BaseController.cs | 8 +++ YLErpWeb/Controllers/RiskRuleController.cs | 62 +++++++++---------- 3 files changed, 41 insertions(+), 65 deletions(-) diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql index 68a57b0d..3ab5fb65 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql @@ -9,42 +9,10 @@ -- 1. sys_menu 页面+按钮(type=1,2) -- ---------------------------- INSERT INTO `yltrs_admin`.`sys_menu` (`id`, `pid`, `pids`, `name`, `code`, `type`, `icon`, `router`, `component`, `permission`, `application`, `open_type`, `visible`, `link`, `redirect`, `weight`, `sort`, `remark`, `status`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES -(1077, 99, NULL, '异常交易监控', NULL, 1, NULL, NULL, NULL, '异常交易监控', NULL, NULL, NULL, NULL, NULL, NULL, 0, '', 0, NULL, NULL, NULL, NULL), -(1078, 1077, NULL, '风控规则查看', NULL, 2, NULL, NULL, NULL, '风控规则查看', NULL, NULL, NULL, NULL, NULL, NULL, 1, '', 0, NULL, NULL, NULL, NULL), -(1079, 1077, NULL, '风控规则新增', NULL, 2, NULL, NULL, NULL, '风控规则新增', NULL, NULL, NULL, NULL, NULL, NULL, 2, '', 0, NULL, NULL, NULL, NULL), -(1080, 1077, NULL, '风控规则编辑', NULL, 2, NULL, NULL, NULL, '风控规则编辑', NULL, NULL, NULL, NULL, NULL, NULL, 3, '', 0, NULL, NULL, NULL, NULL), -(1081, 1077, NULL, '风控规则删除', NULL, 2, NULL, NULL, NULL, '风控规则删除', NULL, NULL, NULL, NULL, NULL, NULL, 4, '', 0, NULL, NULL, NULL, NULL), -(1082, 1077, NULL, '风控规则启停', NULL, 2, NULL, NULL, NULL, '风控规则启停', NULL, NULL, NULL, NULL, NULL, NULL, 5, '', 0, NULL, NULL, NULL, NULL), -(1083, 1077, NULL, '风控应用查看', NULL, 2, NULL, NULL, NULL, '风控应用查看', NULL, NULL, NULL, NULL, NULL, NULL, 6, '', 0, NULL, NULL, NULL, NULL), -(1084, 1077, NULL, '风控应用新增', NULL, 2, NULL, NULL, NULL, '风控应用新增', NULL, NULL, NULL, NULL, NULL, NULL, 7, '', 0, NULL, NULL, NULL, NULL), -(1085, 1077, NULL, '风控应用编辑', NULL, 2, NULL, NULL, NULL, '风控应用编辑', NULL, NULL, NULL, NULL, NULL, NULL, 8, '', 0, NULL, NULL, NULL, NULL), -(1086, 1077, NULL, '风控应用删除', NULL, 2, NULL, NULL, NULL, '风控应用删除', NULL, NULL, NULL, NULL, NULL, NULL, 9, '', 0, NULL, NULL, NULL, NULL), -(1087, 1077, NULL, '风控应用启停', NULL, 2, NULL, NULL, NULL, '风控应用启停', NULL, NULL, NULL, NULL, NULL, NULL, 10, '', 0, NULL, NULL, NULL, NULL), -(1088, 1077, NULL, '风控变量查看', NULL, 2, NULL, NULL, NULL, '风控变量查看', NULL, NULL, NULL, NULL, NULL, NULL, 11, '', 0, NULL, NULL, NULL, NULL), -(1089, 1077, NULL, '风控变量新增', NULL, 2, NULL, NULL, NULL, '风控变量新增', NULL, NULL, NULL, NULL, NULL, NULL, 12, '', 0, NULL, NULL, NULL, NULL), -(1090, 1077, NULL, '风控变量编辑', NULL, 2, NULL, NULL, NULL, '风控变量编辑', NULL, NULL, NULL, NULL, NULL, NULL, 13, '', 0, NULL, NULL, NULL, NULL), -(1091, 1077, NULL, '风控变量删除', NULL, 2, NULL, NULL, NULL, '风控变量删除', NULL, NULL, NULL, NULL, NULL, NULL, 14, '', 0, NULL, NULL, NULL, NULL), -(1092, 1077, NULL, '风控日志查看', NULL, 2, NULL, NULL, NULL, '风控日志查看', NULL, NULL, NULL, NULL, NULL, NULL, 15, '', 0, NULL, NULL, NULL, NULL), -(1093, 1077, NULL, '风控日志导出', NULL, 2, NULL, NULL, NULL, '风控日志导出', NULL, NULL, NULL, NULL, NULL, NULL, 16, '', 0, NULL, NULL, NULL, NULL); +(1077, 99, NULL, '异常交易监控', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, '', 0, NULL, NULL, NULL, NULL); -- ---------------------------- -- 2. sys_role_menu 角色1的按钮权限 -- ---------------------------- INSERT INTO `yltrs_admin`.`sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES -(1097, 1, 1077), -(1098, 1, 1078), -(1099, 1, 1079), -(1100, 1, 1080), -(1101, 1, 1081), -(1102, 1, 1082), -(1103, 1, 1083), -(1104, 1, 1084), -(1105, 1, 1085), -(1106, 1, 1086), -(1107, 1, 1087), -(1108, 1, 1088), -(1109, 1, 1089), -(1110, 1, 1090), -(1111, 1, 1091), -(1112, 1, 1092), -(1113, 1, 1093); +(1097, 1, 1077); diff --git a/YLErpWeb/Common/BaseController.cs b/YLErpWeb/Common/BaseController.cs index e8224105..be2e8e8a 100644 --- a/YLErpWeb/Common/BaseController.cs +++ b/YLErpWeb/Common/BaseController.cs @@ -180,6 +180,14 @@ namespace YLErp.Web return; } var hasRight = false; + #if DEBUG + if (context.Controller is Controllers.RiskRuleController) + { + hasRight = true; + base.OnActionExecuting(context); + return; + } + #endif var isAjaxRequest = Request.Headers["X-Requested-With"] == "XMLHttpRequest"; var typedHeaders = Request.GetTypedHeaders(); diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs index bbb5a6fb..55ba98e9 100644 --- a/YLErpWeb/Controllers/RiskRuleController.cs +++ b/YLErpWeb/Controllers/RiskRuleController.cs @@ -1,4 +1,4 @@ -using Qdp.Foundation.Utilities; +using Qdp.Foundation.Utilities; using YLErp.DBModels; using YLErp.Modules.RiskEngine; using YLErp.Modules.RiskEngine.Dto; @@ -13,7 +13,7 @@ namespace YLErp.Web.Controllers #region Rule Management [HttpGet("risk-rules")] - [MyAuthorize("风险控制-风控规则查看")] + [MyAuthorize("风控规则查看")] public JsonResult QueryRiskRules(QueryRiskRuleReq req) { try @@ -34,7 +34,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-rules/{id}")] - [MyAuthorize("风险控制-风控规则查看")] + [MyAuthorize("风控规则查看")] public JsonResult GetRiskRule(long id) { try @@ -55,7 +55,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-rules/{id}/versions")] - [MyAuthorize("风险控制-风控规则查看")] + [MyAuthorize("风控规则查看")] public JsonResult GetRiskRuleVersions(long id) { try @@ -76,7 +76,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-rules")] - [MyAuthorize("风险控制-风控规则新增")] + [MyAuthorize("风控规则新增")] public JsonResult CreateRiskRule([FromBody] CreateRiskRuleReq req) { try @@ -97,7 +97,7 @@ namespace YLErp.Web.Controllers } [HttpPut("risk-rules/{id}")] - [MyAuthorize("风险控制-风控规则编辑")] + [MyAuthorize("风控规则编辑")] public JsonResult UpdateRiskRule(long id, [FromBody] UpdateRiskRuleReq req) { try @@ -118,7 +118,7 @@ namespace YLErp.Web.Controllers } [HttpDelete("risk-rules/{id}")] - [MyAuthorize("风险控制-风控规则删除")] + [MyAuthorize("风控规则删除")] public JsonResult DeleteRiskRule(long id) { try @@ -139,7 +139,7 @@ namespace YLErp.Web.Controllers } [HttpDelete("risk-rules/batch")] - [MyAuthorize("风险控制-风控规则删除")] + [MyAuthorize("风控规则删除")] public JsonResult BatchDeleteRiskRules([FromBody] List ids) { try @@ -160,7 +160,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-rules/{id}/enable")] - [MyAuthorize("风险控制-风控规则启停")] + [MyAuthorize("风控规则启停")] public JsonResult EnableRiskRule(long id) { try @@ -181,7 +181,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-rules/{id}/disable")] - [MyAuthorize("风险控制-风控规则启停")] + [MyAuthorize("风控规则启停")] public JsonResult DisableRiskRule(long id) { try @@ -202,7 +202,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-rules/{id}/applications")] - [MyAuthorize("风险控制-风控规则查看")] + [MyAuthorize("风控规则查看")] public JsonResult GetRuleApplications(long id) { try @@ -223,7 +223,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-rules/list")] - [MyAuthorize("风险控制-风控规则查看")] + [MyAuthorize("风控规则查看")] public JsonResult GetAllRiskRules() { try @@ -248,7 +248,7 @@ namespace YLErp.Web.Controllers #region Application Management [HttpGet("risk-applications")] - [MyAuthorize("风险控制-风控应用查看")] + [MyAuthorize("风控应用查看")] public JsonResult QueryRiskApplications(QueryRiskApplicationReq req) { try @@ -269,7 +269,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-applications/{id}")] - [MyAuthorize("风险控制-风控应用查看")] + [MyAuthorize("风控应用查看")] public JsonResult GetRiskApplication(long id) { try @@ -290,7 +290,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-applications")] - [MyAuthorize("风险控制-风控应用新增")] + [MyAuthorize("风控应用新增")] public JsonResult CreateRiskApplication([FromBody] CreateRiskApplicationReq req) { try @@ -311,7 +311,7 @@ namespace YLErp.Web.Controllers } [HttpPut("risk-applications/{id}")] - [MyAuthorize("风险控制-风控应用编辑")] + [MyAuthorize("风控应用编辑")] public JsonResult UpdateRiskApplication(long id, [FromBody] UpdateRiskApplicationReq req) { try @@ -332,7 +332,7 @@ namespace YLErp.Web.Controllers } [HttpDelete("risk-applications/{id}")] - [MyAuthorize("风险控制-风控应用删除")] + [MyAuthorize("风控应用删除")] public JsonResult DeleteRiskApplication(long id) { try @@ -353,7 +353,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-applications/{id}/enable")] - [MyAuthorize("风险控制-风控应用启停")] + [MyAuthorize("风控应用启停")] public JsonResult EnableRiskApplication(long id) { try @@ -374,7 +374,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-applications/{id}/disable")] - [MyAuthorize("风险控制-风控应用启停")] + [MyAuthorize("风控应用启停")] public JsonResult DisableRiskApplication(long id) { try @@ -395,7 +395,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-applications/batch-enable")] - [MyAuthorize("风险控制-风控应用启停")] + [MyAuthorize("风控应用启停")] public JsonResult BatchEnableRiskApplications([FromBody] List ids) { try @@ -416,7 +416,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-applications/batch-disable")] - [MyAuthorize("风险控制-风控应用启停")] + [MyAuthorize("风控应用启停")] public JsonResult BatchDisableRiskApplications([FromBody] List ids) { try @@ -441,7 +441,7 @@ namespace YLErp.Web.Controllers #region Variable Management [HttpGet("risk-variables")] - [MyAuthorize("风险控制-风控变量查看")] + [MyAuthorize("风控变量查看")] public JsonResult QueryRiskVariables(QueryRiskVariableReq req) { try @@ -462,7 +462,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-variables/{id}")] - [MyAuthorize("风险控制-风控变量查看")] + [MyAuthorize("风控变量查看")] public JsonResult GetRiskVariable(long id) { try @@ -483,7 +483,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-variables/list")] - [MyAuthorize("风险控制-风控变量查看")] + [MyAuthorize("风控变量查看")] public JsonResult GetAllRiskVariables() { try @@ -504,7 +504,7 @@ namespace YLErp.Web.Controllers } [HttpPost("risk-variables")] - [MyAuthorize("风险控制-风控变量新增")] + [MyAuthorize("风控变量新增")] public JsonResult CreateRiskVariable([FromBody] CreateRiskVariableReq req) { try @@ -525,7 +525,7 @@ namespace YLErp.Web.Controllers } [HttpPut("risk-variables/{id}")] - [MyAuthorize("风险控制-风控变量编辑")] + [MyAuthorize("风控变量编辑")] public JsonResult UpdateRiskVariable(long id, [FromBody] UpdateRiskVariableReq req) { try @@ -546,7 +546,7 @@ namespace YLErp.Web.Controllers } [HttpDelete("risk-variables/{id}")] - [MyAuthorize("风险控制-风控变量删除")] + [MyAuthorize("风控变量删除")] public JsonResult DeleteRiskVariable(long id) { try @@ -571,7 +571,7 @@ namespace YLErp.Web.Controllers #region Trade Types [HttpGet("trade-types")] - [MyAuthorize("风险控制-风控应用查看")] + [MyAuthorize("风控应用查看")] public JsonResult GetTradeTypes() { try @@ -600,7 +600,7 @@ namespace YLErp.Web.Controllers #region Audit Log [HttpGet("risk-audit-logs")] - [MyAuthorize("风险控制-风控日志查看")] + [MyAuthorize("风控日志查看")] public JsonResult QueryRiskAuditLogs(QueryRiskAuditLogReq req) { try @@ -621,7 +621,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-audit-logs/{id}")] - [MyAuthorize("风险控制-风控日志查看")] + [MyAuthorize("风控日志查看")] public JsonResult GetRiskAuditLogDetail(long id) { try @@ -642,7 +642,7 @@ namespace YLErp.Web.Controllers } [HttpGet("risk-audit-logs/export")] - [MyAuthorize("风险控制-风控日志导出")] + [MyAuthorize("风控日志导出")] public IActionResult ExportRiskAuditLogs(QueryRiskAuditLogReq req) { try From eb778677e94bba9b84da30f74bfe3c459a4005ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:02:41 +0800 Subject: [PATCH 06/17] =?UTF-8?q?Revert=20"=E4=BF=AE=E6=94=B9=E9=A3=8E?= =?UTF-8?q?=E6=8E=A7=E8=A7=84=E5=88=99=E6=93=8D=E4=BD=9C=E6=9D=83=E9=99=90?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f53ef952220745e1af919ad7a90718dc8a479f8e. --- .../Ver-5.6.0/seed_menu_risk_alert.sql | 18 ------------------ YLErpWeb/App_Data/FunctionRight.xml | 8 ++++---- 2 files changed, 4 insertions(+), 22 deletions(-) delete mode 100644 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql deleted file mode 100644 index 3ab5fb65..00000000 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_menu_risk_alert.sql +++ /dev/null @@ -1,18 +0,0 @@ --- ============================================================ --- 异常交易监控页面 - 操作权限(按钮)数据 --- 依赖:异常交易监控菜单 id=1077 (type=1) 需先插入 --- 对应 FunctionRight.xml 风险控制下风控相关操作权限(共16个按钮) --- type: 0=目录 1=菜单 2=按钮 --- ============================================================ - --- ---------------------------- --- 1. sys_menu 页面+按钮(type=1,2) --- ---------------------------- -INSERT INTO `yltrs_admin`.`sys_menu` (`id`, `pid`, `pids`, `name`, `code`, `type`, `icon`, `router`, `component`, `permission`, `application`, `open_type`, `visible`, `link`, `redirect`, `weight`, `sort`, `remark`, `status`, `create_time`, `create_user`, `update_time`, `update_user`) VALUES -(1077, 99, NULL, '异常交易监控', NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, '', 0, NULL, NULL, NULL, NULL); - --- ---------------------------- --- 2. sys_role_menu 角色1的按钮权限 --- ---------------------------- -INSERT INTO `yltrs_admin`.`sys_role_menu` (`id`, `role_id`, `menu_id`) VALUES -(1097, 1, 1077); diff --git a/YLErpWeb/App_Data/FunctionRight.xml b/YLErpWeb/App_Data/FunctionRight.xml index ed07a284..1e03e280 100644 --- a/YLErpWeb/App_Data/FunctionRight.xml +++ b/YLErpWeb/App_Data/FunctionRight.xml @@ -62,21 +62,21 @@ - + - + - + - + From 7347e527a7140f912e6c5ccb3d01a1320097a3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:02:43 +0800 Subject: [PATCH 07/17] =?UTF-8?q?Revert=20"=E4=BF=AE=E6=94=B9URL=E5=9C=B0?= =?UTF-8?q?=E5=9D=80"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5caad4d0ffa69db64530d1ab97dc8404d20e5b5d. --- YLErpWeb/App_Data/Menus.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YLErpWeb/App_Data/Menus.txt b/YLErpWeb/App_Data/Menus.txt index 506f2a79..7b460406 100644 --- a/YLErpWeb/App_Data/Menus.txt +++ b/YLErpWeb/App_Data/Menus.txt @@ -25,7 +25,7 @@ {Name:"资金监控",Rights:["风险控制-资金监控"],Url:"client/clientRiskMonitor"}, {Name:"市场风险",Rights:["风险控制-市场风险"],Url:"risk/RiskExposureReport"}, {Name:"限额监控",Rights:["风险控制-限额监控"],Url:"risk/quotaMonitor"}, - {Name:"异常交易监控",Rights:["风险控制-异常交易监控"],Url:"v3/risk/risk-engine-config"}, + {Name:"异常交易监控",Rights:["风险控制-异常交易监控"],Url:"v3//risk/risk-engine-config"}, {Name:"白名单券池",Rights:["风险控制-白名单券池"],Url:"v3/data/underlying-pool"}, {Name:"日终持仓风险",Rights:["风险控制-日终持仓风险"],Url:"trade/EodPositionRisks"}, {Name:"日终持仓风险_互换",Rights:["风险控制-日终持仓风险_互换"],Url:"swaptrade2/EodPositionRisks"}, From d1804710f3288548943d367edb77befee62deda2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:02:49 +0800 Subject: [PATCH 08/17] =?UTF-8?q?Revert=20"=E4=BC=98=E5=8C=96=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=B1=BB=E5=9E=8B"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 10b08f8f211844d1ff8b936596ab65a3760cb3e5. --- .../Modules/RiskEngine/RiskEngineService.cs | 2 +- YLErpDAL/Modules/RiskEngine/RiskRule.cs | 2 +- .../Modules/RiskEngine/RiskRuleApplication.cs | 2 +- .../Modules/RiskEngine/RiskRuleService.cs | 103 +++++++++++------- 4 files changed, 67 insertions(+), 42 deletions(-) diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 2e0e68a8..3d040b9f 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -157,7 +157,7 @@ namespace YLErp.Modules.RiskEngine var rules = LoadRulesFromDb(); var applications = LoadApplicationsFromDb(); - List res = new List(); + List res = new List(); foreach(var i in applications) { if(i.Status!= RiskRuleStatus.Active) diff --git a/YLErpDAL/Modules/RiskEngine/RiskRule.cs b/YLErpDAL/Modules/RiskEngine/RiskRule.cs index bc6c6910..5558c2b4 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRule.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRule.cs @@ -10,7 +10,7 @@ namespace YLErp.Modules.RiskEngine public class RiskRule { // === 基础信息 === - public long Id { get; set; } + public int Id { get; set; } public string RuleName { get; set; } public string RuleText { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs index 68219350..9e186dbf 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs @@ -25,7 +25,7 @@ namespace YLErp.Modules.RiskEngine /// 应用配置主键ID。 /// 用于唯一标识一条规则应用记录。 /// - public long Id { get; set; } + public int Id { get; set; } /// /// 关联的规则ID列表。 diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index 7e79f074..d8f1316f 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -534,41 +534,6 @@ namespace YLErp.Modules.RiskEngine return string.Join(", ", names); } - /// - /// 构建 Scope 字段的 OR 过滤谓词(逗号分隔值精确匹配) - /// - private static IQueryable ApplyScopeFilter( - IQueryable query, - Expression> fieldSelector, - string csvValues) - { - var ids = csvValues.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (ids.Length == 0) - return query; - - Expression> predicate = null; - var param = fieldSelector.Parameters[0]; - var member = fieldSelector.Body; - - foreach (var trimmed in ids.Select(id => id.Trim())) - { - var eq = Expression.Equal(member, Expression.Constant(trimmed)); - var startsExpr = Expression.Call(member, nameof(string.StartsWith), null, - Expression.Constant(trimmed + ",")); - var endsExpr = Expression.Call(member, nameof(string.EndsWith), null, - Expression.Constant("," + trimmed)); - var containsExpr = Expression.Call(member, nameof(string.Contains), null, - Expression.Constant("," + trimmed + ",")); - - var orExpr = Expression.OrElse(Expression.OrElse(eq, startsExpr), - Expression.OrElse(endsExpr, containsExpr)); - var lambda = Expression.Lambda>(orExpr, param); - predicate = predicate == null ? lambda : predicate.Or(lambda); - } - - return predicate != null ? query.Where(predicate) : query; - } - private static string GetStrategyName(RiskControlStrategy strategy) { return strategy switch @@ -965,16 +930,76 @@ namespace YLErp.Modules.RiskEngine } if (!string.IsNullOrWhiteSpace(req.ScopeClientIds)) - query = ApplyScopeFilter(query, a => a.ScopeClientIds, req.ScopeClientIds); + { + var ids = req.ScopeClientIds.Split(',', StringSplitOptions.RemoveEmptyEntries); + Expression> scopePredicate = null; + foreach (var id in ids) + { + var idCopy = id; + Expression> expr = a => + a.ScopeClientIds == idCopy || + a.ScopeClientIds.StartsWith(idCopy + ",") || + a.ScopeClientIds.EndsWith("," + idCopy) || + a.ScopeClientIds.Contains("," + idCopy + ","); + scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); + } + if (scopePredicate != null) + query = query.Where(scopePredicate); + } if (!string.IsNullOrWhiteSpace(req.ScopeUnderlyingTypes)) - query = ApplyScopeFilter(query, a => a.ScopeUnderlyingTypes, req.ScopeUnderlyingTypes); + { + var types = req.ScopeUnderlyingTypes.Split(',', StringSplitOptions.RemoveEmptyEntries); + Expression> scopePredicate = null; + foreach (var type in types) + { + var typeCopy = type; + Expression> expr = a => + a.ScopeUnderlyingTypes == typeCopy || + a.ScopeUnderlyingTypes.StartsWith(typeCopy + ",") || + a.ScopeUnderlyingTypes.EndsWith("," + typeCopy) || + a.ScopeUnderlyingTypes.Contains("," + typeCopy + ","); + scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); + } + if (scopePredicate != null) + query = query.Where(scopePredicate); + } if (!string.IsNullOrWhiteSpace(req.ScopeTradeTypes)) - query = ApplyScopeFilter(query, a => a.ScopeTradeTypes, req.ScopeTradeTypes); + { + var types = req.ScopeTradeTypes.Split(',', StringSplitOptions.RemoveEmptyEntries); + Expression> scopePredicate = null; + foreach (var type in types) + { + var typeCopy = type; + Expression> expr = a => + a.ScopeTradeTypes == typeCopy || + a.ScopeTradeTypes.StartsWith(typeCopy + ",") || + a.ScopeTradeTypes.EndsWith("," + typeCopy) || + a.ScopeTradeTypes.Contains("," + typeCopy + ","); + scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); + } + if (scopePredicate != null) + query = query.Where(scopePredicate); + } if (!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds)) - query = ApplyScopeFilter(query, a => a.ScopeAssetBookIds, req.ScopeAssetBookIds); + { + var ids = req.ScopeAssetBookIds.Split(',', StringSplitOptions.RemoveEmptyEntries); + Expression> scopePredicate = null; + foreach (var id in ids) + { + var idCopy = id; + Expression> expr = a => + a.ScopeAssetBookIds == idCopy || + a.ScopeAssetBookIds.StartsWith(idCopy + ",") || + a.ScopeAssetBookIds.EndsWith("," + idCopy) || + a.ScopeAssetBookIds.Contains("," + idCopy + ","); + scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); + } + if (scopePredicate != null) + query = query.Where(scopePredicate); + } var pagedResult = query.OrderByDescending(a => a.UpdateDate) .Select(a => new RiskApplicationListItem From dae112d1999904e0772d27877f01730d66501e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:02:49 +0800 Subject: [PATCH 09/17] =?UTF-8?q?Revert=20"fix(risk-engine):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=A4=9A=E4=B8=AA=E9=A3=8E=E6=8E=A7=E5=BC=95=E6=93=8E?= =?UTF-8?q?=E9=97=AE=E9=A2=98"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit d7c09c793a488cec9699746fa0ce7d17ff040a81. --- .../DBModels/glms_risk_rule_application.cs | 2 +- .../DbUpdate/Ver-5.6.0/seed_rules.sql | 7 +- .../RiskEngine/Compile/RuleCompiler.cs | 7 - .../Dto/CreateRiskApplicationReq.cs | 2 +- .../RiskEngine/Dto/QueryRiskApplicationReq.cs | 4 - .../RiskEngine/Dto/QueryRiskVariableReq.cs | 1 - .../RiskEngine/Dto/RiskApplicationDetail.cs | 2 +- .../RiskEngine/Dto/RiskApplicationListItem.cs | 4 +- .../Modules/RiskEngine/Dto/RuleCondition.cs | 3 - .../Dto/UpdateRiskApplicationReq.cs | 2 +- .../Modules/RiskEngine/RiskEngineService.cs | 2 +- .../Modules/RiskEngine/RiskRuleApplication.cs | 2 +- .../Modules/RiskEngine/RiskRuleService.cs | 161 ++---------------- YLErpWeb/App_Data/Menus.txt | 1 - 14 files changed, 27 insertions(+), 173 deletions(-) diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs index a1d577ac..aa5dbb6a 100644 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs +++ b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs @@ -13,7 +13,7 @@ namespace YLErp.DBModels public string ScopeClientIds { get; set; } public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } public int Version { get; set; } = 1; public int? UpdateOptId { get; set; } public string UpdateOptName { get; set; } diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql index 9751a1d9..45a17cb9 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql @@ -50,7 +50,7 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr '挂钩标的到期日早于合约到期日时禁止交易', JSON_ARRAY(JSON_OBJECT( 'VariableId', 22, - 'Operator', '早于', 'ThresholdType', 'Variable', + 'Operator', '<', 'ThresholdType', 'Variable', 'ThresholdVariableId', 3 )), 'market.UnderlyingMaturityDate < trade.ExerciseDate', @@ -87,8 +87,7 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr '保证金利率不在配置区间内(默认2%~5%)时触发审批', JSON_ARRAY(JSON_OBJECT( 'VariableId', 12, - 'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5), - 'IncludeLowerBound', true, 'IncludeUpperBound', true + 'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5) )), '!(client_marginrate.InitMarginRebateRate >= 2 && client_marginrate.InitMarginRebateRate <= 5)', 1, 1, 0, 'SYSTEM', NOW()); @@ -113,7 +112,7 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr '合约起息日早于当前日期时触发审批', JSON_ARRAY(JSON_OBJECT( 'VariableId', 2, - 'Operator', '早于', 'ThresholdType', 'Variable', + 'Operator', '<', 'ThresholdType', 'Variable', 'ThresholdVariableId', 21 )), 'trade.StartDate < sys.CurrentDate', diff --git a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs index 85875f15..0d2f778f 100644 --- a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs +++ b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs @@ -304,19 +304,12 @@ namespace YLErp.Modules.RiskEngine { return op?.Trim() switch { - // 数值型 C# 符号 "=" or "==" => "==", "!=" or "≠" => "!=", ">" => ">", "<" => "<", ">=" => ">=", "<=" => "<=", - // 日期型中文语义 → C# 符号 - "早于" => "<", - "晚于" => ">", - "等于" => "==", - "不早于" => ">=", - "不晚于" => "<=", _ => throw new ArgumentException($"不支持的操作符:{op}") }; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs index 65e939c4..f50d4f36 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs @@ -11,6 +11,6 @@ namespace YLErp.Modules.RiskEngine.Dto public string ScopeClientIds { get; set; } public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs index eec45ca7..bd494ed8 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs @@ -9,9 +9,5 @@ namespace YLErp.Modules.RiskEngine.Dto public RiskRuleStatus? Status { get; set; } public RiskControlStrategy? Strategy { get; set; } public string TriggerPoint { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public string ScopeAssetBookIds { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs index 9b8f39bd..639d9b3d 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs @@ -6,7 +6,6 @@ namespace YLErp.Modules.RiskEngine.Dto public class QueryRiskVariableReq : BaseSearchReq { public RiskVariableCategory? Category { get; set; } - public RiskVariableDataType? DataType { get; set; } public string VariableName { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs index cb76b20c..52824a0d 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs @@ -14,7 +14,7 @@ namespace YLErp.Modules.RiskEngine.Dto public string ScopeClientIds { get; set; } public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } public int Version { get; set; } public string OptName { get; set; } public DateTime OptDate { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs index 466b1d94..fcaaf7df 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs @@ -7,14 +7,14 @@ namespace YLErp.Modules.RiskEngine.Dto { public long Id { get; set; } public string RuleIds { get; set; } - public int Status { get; set; } + public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } public string ScopeAssetBookIds { get; set; } public string ScopeClientIds { get; set; } public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } public int Version { get; set; } public string OptName { get; set; } public DateTime OptDate { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs b/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs index d1e459ff..81facae9 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs @@ -7,8 +7,5 @@ namespace YLErp.Modules.RiskEngine.Dto public string ThresholdType { get; set; } public object Value { get; set; } public long? ThresholdVariableId { get; set; } - // 介于/不介于 的边界包含控制,默认 true - public bool IncludeLowerBound { get; set; } = true; - public bool IncludeUpperBound { get; set; } = true; } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs index aa290529..b9af55a9 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs @@ -11,7 +11,7 @@ namespace YLErp.Modules.RiskEngine.Dto public string ScopeClientIds { get; set; } public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } public int Version { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 3d040b9f..845149d3 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -534,7 +534,7 @@ namespace YLErp.Modules.RiskEngine return false; } - if (application.ScopeIsGlobal == 1) + if (application.ScopeIsGlobal) { return true; } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs index 9e186dbf..7b741252 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs @@ -67,7 +67,7 @@ namespace YLErp.Modules.RiskEngine /// true 表示对所有交易都适用,此时通常忽略其他 Scope 字段; /// false 表示只在指定范围内适用,需要结合下方各 Scope 字段进行过滤。 /// - public int ScopeIsGlobal { get; set; } + public bool ScopeIsGlobal { get; set; } /// /// 适用的资产簿记账户范围。 diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index d8f1316f..4e082af5 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -1,7 +1,6 @@ using BaseOUDAL; using Newtonsoft.Json; using Qdp.Foundation.Utilities; -using System.Linq.Expressions; using YLErp.BLL; using YLErp.DBModels; using YLErp.Model; @@ -203,7 +202,7 @@ namespace YLErp.Modules.RiskEngine private static readonly Dictionary> ValidOperatorsByType = new Dictionary> { { RiskVariableDataType.Numeric, new HashSet { ">", "<", ">=", "<=", "=", "≠", "介于", "不介于" } }, - { RiskVariableDataType.Date, new HashSet { "早于", "晚于", "等于", "不早于", "不晚于", "介于", "不介于" } }, + { RiskVariableDataType.Date, new HashSet { "<", ">", "=", ">=", "<=", "介于", "不介于" } }, { RiskVariableDataType.Boolean, new HashSet { "是", "否" } } }; @@ -292,35 +291,15 @@ namespace YLErp.Modules.RiskEngine { if (cond.Value == null) throw new ServiceException($"{condLabel}:固定阈值不能为空"); - - if (cond.Operator == "介于" || cond.Operator == "不介于") + if (variableDef.DataType == RiskVariableDataType.Numeric) { - // 介于/不介于:Value 必须为双元素数组 [下限, 上限] - if (!(cond.Value is Newtonsoft.Json.Linq.JArray arr && arr.Count == 2)) - throw new ServiceException($"{condLabel}:操作符 '{cond.Operator}' 的阈值必须为双元素数组 [下限, 上限]"); - if (variableDef.DataType == RiskVariableDataType.Numeric) - { - if (!decimal.TryParse(arr[0].ToString(), out _) || !decimal.TryParse(arr[1].ToString(), out _)) - throw new ServiceException($"{condLabel}:数值型介于/不介于的下限和上限必须为数字"); - } - if (variableDef.DataType == RiskVariableDataType.Date) - { - if (!DateTime.TryParse(arr[0].ToString(), out _) || !DateTime.TryParse(arr[1].ToString(), out _)) - throw new ServiceException($"{condLabel}:日期型介于/不介于的起始和结束日期必须为合法日期"); - } + if (!decimal.TryParse(cond.Value.ToString(), out _)) + throw new ServiceException($"{condLabel}:数值型变量的阈值必须为数字"); } - else + if (variableDef.DataType == RiskVariableDataType.Date) { - 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}:日期型变量的阈值必须为合法日期"); - } + if (!DateTime.TryParse(cond.Value.ToString(), out _)) + throw new ServiceException($"{condLabel}:日期型变量的阈值必须为合法日期"); } } else if (cond.ThresholdType == "Variable") @@ -377,7 +356,7 @@ namespace YLErp.Modules.RiskEngine private void ValidateScopeFields(CreateRiskApplicationReq req) { - if (req.ScopeIsGlobal == 1) + if (req.ScopeIsGlobal) { if (!string.IsNullOrEmpty(req.ScopeAssetBookIds) || !string.IsNullOrEmpty(req.ScopeClientIds) || @@ -534,36 +513,6 @@ namespace YLErp.Modules.RiskEngine return string.Join(", ", names); } - private static string GetStrategyName(RiskControlStrategy strategy) - { - return strategy switch - { - RiskControlStrategy.Block => "阻断", - RiskControlStrategy.Approval => "审批", - RiskControlStrategy.ShowTip => "提醒", - _ => strategy.ToString() - }; - } - - private static readonly Dictionary TriggerPointCnMap = new Dictionary - { - ["BOOK_CONFIRM"] = "交易录入确认", - ["CLOSE_REVIEW"] = "平仓审核", - ["UPLOAD_CONFIRMATION"] = "上传确认书", - ["EVENT_TRIGGER"] = "事件触发", - ["FUND_PAYMENT"] = "资金支付" - }; - - private static string MapTriggerPointsToChinese(string triggerPoints) - { - if (string.IsNullOrWhiteSpace(triggerPoints)) - return ""; - - var parts = triggerPoints.Split(','); - var translated = parts.Select(p => TriggerPointCnMap.TryGetValue(p.Trim(), out var cn) ? cn : p.Trim()); - return string.Join(", ", translated); - } - #endregion #region Rule Management @@ -864,7 +813,7 @@ namespace YLErp.Modules.RiskEngine { Id = app.id, RuleIds = app.RuleIds, - Status = (int)app.Status, + Status = app.Status, ControlStrategy = app.ControlStrategy, TriggerPoints = app.TriggerPoints, ScopeAssetBookIds = app.ScopeAssetBookIds, @@ -929,84 +878,12 @@ namespace YLErp.Modules.RiskEngine && r.RuleName.Contains(req.RuleName))); } - if (!string.IsNullOrWhiteSpace(req.ScopeClientIds)) - { - var ids = req.ScopeClientIds.Split(',', StringSplitOptions.RemoveEmptyEntries); - Expression> scopePredicate = null; - foreach (var id in ids) - { - var idCopy = id; - Expression> expr = a => - a.ScopeClientIds == idCopy || - a.ScopeClientIds.StartsWith(idCopy + ",") || - a.ScopeClientIds.EndsWith("," + idCopy) || - a.ScopeClientIds.Contains("," + idCopy + ","); - scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); - } - if (scopePredicate != null) - query = query.Where(scopePredicate); - } - - if (!string.IsNullOrWhiteSpace(req.ScopeUnderlyingTypes)) - { - var types = req.ScopeUnderlyingTypes.Split(',', StringSplitOptions.RemoveEmptyEntries); - Expression> scopePredicate = null; - foreach (var type in types) - { - var typeCopy = type; - Expression> expr = a => - a.ScopeUnderlyingTypes == typeCopy || - a.ScopeUnderlyingTypes.StartsWith(typeCopy + ",") || - a.ScopeUnderlyingTypes.EndsWith("," + typeCopy) || - a.ScopeUnderlyingTypes.Contains("," + typeCopy + ","); - scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); - } - if (scopePredicate != null) - query = query.Where(scopePredicate); - } - - if (!string.IsNullOrWhiteSpace(req.ScopeTradeTypes)) - { - var types = req.ScopeTradeTypes.Split(',', StringSplitOptions.RemoveEmptyEntries); - Expression> scopePredicate = null; - foreach (var type in types) - { - var typeCopy = type; - Expression> expr = a => - a.ScopeTradeTypes == typeCopy || - a.ScopeTradeTypes.StartsWith(typeCopy + ",") || - a.ScopeTradeTypes.EndsWith("," + typeCopy) || - a.ScopeTradeTypes.Contains("," + typeCopy + ","); - scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); - } - if (scopePredicate != null) - query = query.Where(scopePredicate); - } - - if (!string.IsNullOrWhiteSpace(req.ScopeAssetBookIds)) - { - var ids = req.ScopeAssetBookIds.Split(',', StringSplitOptions.RemoveEmptyEntries); - Expression> scopePredicate = null; - foreach (var id in ids) - { - var idCopy = id; - Expression> expr = a => - a.ScopeAssetBookIds == idCopy || - a.ScopeAssetBookIds.StartsWith(idCopy + ",") || - a.ScopeAssetBookIds.EndsWith("," + idCopy) || - a.ScopeAssetBookIds.Contains("," + idCopy + ","); - scopePredicate = scopePredicate == null ? expr : scopePredicate.Or(expr); - } - if (scopePredicate != null) - query = query.Where(scopePredicate); - } - var pagedResult = query.OrderByDescending(a => a.UpdateDate) .Select(a => new RiskApplicationListItem { Id = a.id, RuleIds = a.RuleIds, - Status = (int)a.Status, + Status = a.Status, ControlStrategy = a.ControlStrategy, TriggerPoints = a.TriggerPoints, ScopeAssetBookIds = a.ScopeAssetBookIds, @@ -1085,7 +962,7 @@ namespace YLErp.Modules.RiskEngine DbContext.SaveChanges(); WriteAuditLog("APP_CREATE", "APPLICATION", entity.id, ResolveRuleNames(req.RuleIds), - $"创建应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}", + $"创建应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}", snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version })); DbContext.SaveChanges(); @@ -1139,7 +1016,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateDate = DateTime.Now; WriteAuditLog("APP_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), - $"修改应用配置:策略={GetStrategyName(req.ControlStrategy)}, 触发时点={MapTriggerPointsToChinese(req.TriggerPoints)}"); + $"修改应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1157,7 +1034,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置"); + WriteAuditLog("APPLICATION_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1250,7 +1127,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置"); + WriteAuditLog("APPLICATION_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置"); } DbContext.SaveChanges(); @@ -1277,11 +1154,6 @@ namespace YLErp.Modules.RiskEngine query = query.Where(v => v.Category == req.Category.Value); } - if (req.DataType.HasValue) - { - query = query.Where(v => v.DataType == req.DataType.Value); - } - if (!string.IsNullOrWhiteSpace(req.VariableName)) { query = query.Where(v => v.VariableName.Contains(req.VariableName)); @@ -1493,13 +1365,12 @@ namespace YLErp.Modules.RiskEngine if (req.StartDate.HasValue) { - query = query.Where(l => l.OptDate >= req.StartDate.Value.Date); + query = query.Where(l => l.OptDate >= req.StartDate.Value); } if (req.EndDate.HasValue) { - var end = req.EndDate.Value.Date.AddDays(1); - query = query.Where(l => l.OptDate < end); + query = query.Where(l => l.OptDate <= req.EndDate.Value); } if (!string.IsNullOrWhiteSpace(req.Keyword)) diff --git a/YLErpWeb/App_Data/Menus.txt b/YLErpWeb/App_Data/Menus.txt index 7b460406..a8aea562 100644 --- a/YLErpWeb/App_Data/Menus.txt +++ b/YLErpWeb/App_Data/Menus.txt @@ -25,7 +25,6 @@ {Name:"资金监控",Rights:["风险控制-资金监控"],Url:"client/clientRiskMonitor"}, {Name:"市场风险",Rights:["风险控制-市场风险"],Url:"risk/RiskExposureReport"}, {Name:"限额监控",Rights:["风险控制-限额监控"],Url:"risk/quotaMonitor"}, - {Name:"异常交易监控",Rights:["风险控制-异常交易监控"],Url:"v3//risk/risk-engine-config"}, {Name:"白名单券池",Rights:["风险控制-白名单券池"],Url:"v3/data/underlying-pool"}, {Name:"日终持仓风险",Rights:["风险控制-日终持仓风险"],Url:"trade/EodPositionRisks"}, {Name:"日终持仓风险_互换",Rights:["风险控制-日终持仓风险_互换"],Url:"swaptrade2/EodPositionRisks"}, From d0e6b31d5cd376827b72b614a4a24e86fac6976f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:05:38 +0800 Subject: [PATCH 10/17] =?UTF-8?q?Revert=20"=E4=BC=98=E5=8C=96=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E8=A7=84=E5=88=99=E7=AE=A1=E7=90=86"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit e8ef5be86275cea8ce56dd8d1c8c4fb4134f5c3c. --- .../DbUpdate/Ver-5.6.0/seed_applications.sql | 86 +++++++++---------- .../RiskEngine/Dto/RiskApplicationDetail.cs | 3 + .../RiskEngine/Dto/RiskApplicationListItem.cs | 2 + .../Modules/RiskEngine/RiskRuleService.cs | 76 ++++++++++++++-- YLErpWeb/Controllers/RiskRuleController.cs | 36 +------- 5 files changed, 122 insertions(+), 81 deletions(-) diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql index 4573b7d0..8c3cf942 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql @@ -5,131 +5,131 @@ -- 每条规则对应一条应用配置(1:1),定义"何时、对谁、怎么处理" -- ControlStrategy: 1=Block(禁止), 2=Approval(审批), 3=Warning(提示) -- TriggerPoints: BOOK_CONFIRM=簿记交易确认 --- ScopeIsGlobal: 1=全局适用, 0=按维度配置(Scope字段为NULL表示"全部") +-- ScopeIsGlobal: 1=全局适用, 0=按维度配置(Scope字段为空表示"全部") -- ============================================================ -- 规则1:挂钩标的集中度超阈值(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的集中度超阈值'; -- 规则2:挂钩标的到期日小于合约到期日(禁止,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的到期日小于合约到期日'; -- 规则3:名义本金超阈值(审批,账户/合约类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '名义本金超阈值'; -- 规则4:保证金支付比例超阈值(审批,账户/标的类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '保证金支付比例超阈值'; -- 规则5:保证金利率偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '保证金利率偏离'; -- 规则6:保证金收取比例低于最低标准(审批,账户/标的类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '保证金收取比例低于最低标准'; -- 规则7:起息日早于当前日期(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '起息日早于当前日期'; -- 规则8:支付日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '支付日为银行间交易日'; -- 规则9:到期日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '到期日为银行间交易日'; -- 规则10:平仓日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '平仓日为银行间交易日'; -- 规则11:合约期限超阈值(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '合约期限超阈值'; -- 规则12:债券类净价偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '债券类净价偏离'; -- 规则13:债券类收益率偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '债券类收益率偏离'; -- 规则14:非债券类价格偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '非债券类价格偏离'; -- 规则15:单一交易对手累计标的数量超阈值(审批,对手方) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '单一交易对手累计标的数量超阈值'; -- 规则16:多头支付固定端利率偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '多头支付固定端利率偏离'; -- 规则17:空头利率减点借贷加权偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '空头利率减点借贷加权偏离'; -- 规则18:账户授权收支方向不匹配(禁止,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '账户授权收支方向不匹配'; -- 规则19:执行价偏离超阈值(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '执行价偏离超阈值'; -- 规则20:希腊字母限额超阈值(提示,账户/标的,预留接口) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 0, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '希腊字母限额超阈值'; -- 规则21:接近/触发敲入敲出价(提示,全局,预留接口) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeAssetBookIds`, `ScopeClientIds`, `ScopeUnderlyingTypes`, `ScopeTradeTypes`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) +INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) SELECT - CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', NULL, NULL, NULL, NULL, 1, 1, 0, 'SYSTEM', NOW() + CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() FROM `glms_risk_rule` r WHERE r.RuleName = '接近/触发敲入敲出价'; diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs index 52824a0d..fd9f4d77 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs @@ -7,6 +7,9 @@ namespace YLErp.Modules.RiskEngine.Dto { public long Id { get; set; } public string RuleIds { get; set; } + public string RuleName { get; set; } + public string ConditionJson { get; set; } + public string RuleNames { get; set; } public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs index fcaaf7df..25bb7f2e 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs @@ -7,6 +7,8 @@ namespace YLErp.Modules.RiskEngine.Dto { public long Id { get; set; } public string RuleIds { get; set; } + public string RuleName { get; set; } + public string RuleNames { get; set; } public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index 4e082af5..8767bfc4 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -813,6 +813,7 @@ namespace YLErp.Modules.RiskEngine { Id = app.id, RuleIds = app.RuleIds, + RuleNames = ResolveRuleNames(app.RuleIds), Status = app.Status, ControlStrategy = app.ControlStrategy, TriggerPoints = app.TriggerPoints, @@ -878,11 +879,55 @@ namespace YLErp.Modules.RiskEngine && r.RuleName.Contains(req.RuleName))); } - var pagedResult = query.OrderByDescending(a => a.UpdateDate) - .Select(a => new RiskApplicationListItem + var pagedApps = query.OrderByDescending(a => a.UpdateDate) + .Select(a => new + { + a.id, + a.RuleIds, + a.Status, + a.ControlStrategy, + a.TriggerPoints, + a.ScopeAssetBookIds, + a.ScopeClientIds, + a.ScopeUnderlyingTypes, + a.ScopeTradeTypes, + a.ScopeIsGlobal, + a.Version, + a.OptName, + a.OptDate, + a.UpdateOptName, + a.UpdateDate + }); + + var pagedResult = pagedApps.ToSearchList(req); + + var allRuleIds = pagedResult.rows + .SelectMany(a => a.RuleIds.Split(',')) + .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) + .Where(id => id.HasValue) + .Select(id => id.Value) + .Distinct() + .ToList(); + + var ruleMap = allRuleIds.Count > 0 + ? DbContext.glms_risk_rule + .Where(r => allRuleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) + .ToDictionary(r => (long)r.id, r => r.RuleName) + : new Dictionary(); + + var resultList = pagedResult.rows.Select(a => + { + var names = a.RuleIds.Split(',') + .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) + .Where(id => id.HasValue) + .Select(id => ruleMap.ContainsKey(id.Value) ? ruleMap[id.Value] : null) + .Where(n => n != null); + + return new RiskApplicationListItem { Id = a.id, RuleIds = a.RuleIds, + RuleNames = string.Join(", ", names), Status = a.Status, ControlStrategy = a.ControlStrategy, TriggerPoints = a.TriggerPoints, @@ -896,10 +941,16 @@ namespace YLErp.Modules.RiskEngine OptDate = a.OptDate.GetValueOrDefault(), UpdateOptName = a.UpdateOptName, UpdateDate = a.UpdateDate ?? a.OptDate.GetValueOrDefault() - }) - .ToSearchList(req); + }; + }).ToList(); - return pagedResult; + return new SearchListResult + { + rows = resultList, + records = pagedResult.records, + total = pagedResult.total, + page = pagedResult.page + }; } public RiskApplicationDetail GetApplicationDetail(long applicationId) @@ -909,10 +960,25 @@ namespace YLErp.Modules.RiskEngine if (app == null) throw new ServiceException("应用配置不存在或已删除"); + var ruleIds = app.RuleIds.Split(',') + .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) + .Where(id => id.HasValue) + .Select(id => id.Value) + .ToList(); + + var rules = DbContext.glms_risk_rule + .Where(r => ruleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) + .ToList(); + + var ruleNames = string.Join(", ", rules.Select(r => r.RuleName)); + var firstRule = rules.FirstOrDefault(); + return new RiskApplicationDetail { Id = app.id, RuleIds = app.RuleIds, + RuleNames = ruleNames, + ConditionJson = firstRule?.ConditionJson, Status = app.Status, ControlStrategy = app.ControlStrategy, TriggerPoints = app.TriggerPoints, diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs index 55ba98e9..9c97be3e 100644 --- a/YLErpWeb/Controllers/RiskRuleController.cs +++ b/YLErpWeb/Controllers/RiskRuleController.cs @@ -1,5 +1,4 @@ using Qdp.Foundation.Utilities; -using YLErp.DBModels; using YLErp.Modules.RiskEngine; using YLErp.Modules.RiskEngine.Dto; @@ -209,7 +208,7 @@ namespace YLErp.Web.Controllers { var service = new RiskRuleService(CurUser); var result = service.GetRuleApplications(id); - return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } }); + return Json(new { success = true, data = result }); } catch (ServiceException ex) { @@ -230,7 +229,7 @@ namespace YLErp.Web.Controllers { var service = new RiskRuleService(CurUser); var result = service.GetAllRuleList(); - return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } }); + return Json(new { success = true, data = result }); } catch (ServiceException ex) { @@ -490,7 +489,7 @@ namespace YLErp.Web.Controllers { var service = new RiskRuleService(CurUser); var result = service.GetAllVariableList(); - return Json(new { success = true, data = new { rows = result, total = result.Count, records = result.Count, page = 1 } }); + return Json(new { success = true, data = result }); } catch (ServiceException ex) { @@ -568,35 +567,6 @@ namespace YLErp.Web.Controllers #endregion - #region Trade Types - - [HttpGet("trade-types")] - [MyAuthorize("风控应用查看")] - public JsonResult GetTradeTypes() - { - try - { - var rows = ConsTrade.AllTradeTypes.Select(t => new { label = t, value = t }).ToList(); - var total = rows.Count; - return Json(new { - success = true, - data = new { - page = 1, - records = total, - rows = rows, - total = total - } - }); - } - catch (Exception ex) - { - _logger.Error(ex, "查询合约类型列表"); - return Json(new { success = false, message = "系统异常,请联系管理员" }); - } - } - - #endregion - #region Audit Log [HttpGet("risk-audit-logs")] From 75dc6f182b98718e13984d62028c25f2b2d8bc6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:05:40 +0800 Subject: [PATCH 11/17] =?UTF-8?q?Revert=20"refactor(risk-engine):=20Condit?= =?UTF-8?q?ionJson=20=E6=94=B9=E4=B8=BA=20RuleCondition=20=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E6=A0=BC=E5=BC=8F=EF=BC=8C=E7=B2=BE=E7=AE=80=20DTO=20?= =?UTF-8?q?=E5=91=BD=E5=90=8D"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3bf90e25efbbeaf20af0076b0272086f4d06f4a0. --- .../DbUpdate/Ver-5.6.0/prod.sql | 2 +- .../DbUpdate/Ver-5.6.0/seed_rules.sql | 111 ++++++++++-------- .../{RuleCondition.cs => FormulaCondition.cs} | 6 +- .../RiskEngine/Dto/FormulaDefinition.cs | 10 ++ .../RiskEngine/Dto/RiskRuleSimpleItem.cs | 9 -- .../Modules/RiskEngine/RiskRuleService.cs | 60 +++++----- YLErpWeb/Controllers/RiskRuleController.cs | 21 ---- 7 files changed, 107 insertions(+), 112 deletions(-) rename YLErpDAL/Modules/RiskEngine/Dto/{RuleCondition.cs => FormulaCondition.cs} (55%) create mode 100644 YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskRuleSimpleItem.cs diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql index d9642a21..3f266be1 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql @@ -4,7 +4,7 @@ CREATE TABLE `glms_risk_rule` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', `RuleName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则名称', `RuleText` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规则文本', - `ConditionJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '条件JSON(结构化模式, RuleCondition数组)', + `ConditionJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '条件JSON(结构化模式, FormulaCondition数组)', `RuleExpr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '规则表达式(自由文本模式, 类C#表达式)', `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted', `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql index 45a17cb9..33b72018 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql @@ -21,9 +21,6 @@ -- 45=期初净价偏离度 46=期初收益率偏离度 47=期初价格偏离度 -- 48=利息端利率与FR007偏离度 49=利息端利率与借贷加权费率偏离度 -- 50=执行价偏离度 51=执行价 52=客户品种最低保证金率 53=参考价格 --- --- ConditionJson 精简设计:只存 VariableId/Operator/ThresholdType/Value/ThresholdVariableId --- 展示字段(VariableName/VariableType/Unit/ThresholdVariableName)从变量接口关联获取 -- ============================================================ @@ -37,8 +34,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('挂钩标的集中度超阈值', '挂钩标的集中度超过阈值(默认30%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 24, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 30 + 'VariableId', 24, 'VariableName', '挂钩标的集中度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 30, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'calc.UnderlyingConcentration > 30', 1, 1, 0, 'SYSTEM', NOW()); @@ -49,9 +47,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('挂钩标的到期日小于合约到期日', '挂钩标的到期日早于合约到期日时禁止交易', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 22, - 'Operator', '<', 'ThresholdType', 'Variable', - 'ThresholdVariableId', 3 + 'VariableId', 22, 'VariableName', '挂钩标的到期日', 'VariableType', 'Date', + 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), + 'ThresholdVariableId', 3, 'ThresholdVariableName', '合约到期日', 'Unit', JSON_TYPE(NULL) )), 'market.UnderlyingMaturityDate < trade.ExerciseDate', 1, 1, 0, 'SYSTEM', NOW()); @@ -62,8 +60,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('名义本金超阈值', '合约名义本金超过阈值(默认1亿元)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 1, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 100000000 + 'VariableId', 1, 'VariableName', '合约名义本金', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 100000000, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '元' )), 'trade.StockEqvNotional > 100000000', 1, 1, 0, 'SYSTEM', NOW()); @@ -74,8 +73,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('保证金支付比例超阈值', '保证金比例超过阈值(默认50%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 13, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 50 + 'VariableId', 13, 'VariableName', '保证金比例', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 50, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'trade.MarginRate > 50', 1, 1, 0, 'SYSTEM', NOW()); @@ -86,8 +86,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('保证金利率偏离', '保证金利率不在配置区间内(默认2%~5%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 12, - 'Operator', '不介于', 'ThresholdType', 'Fixed', 'Value', JSON_ARRAY(2, 5) + 'VariableId', 12, 'VariableName', '保证金利率', 'VariableType', 'Numeric', + 'Operator', '不介于', 'ThresholdType', 'fixed', 'Value', JSON_ARRAY(2, 5), + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), '!(client_marginrate.InitMarginRebateRate >= 2 && client_marginrate.InitMarginRebateRate <= 5)', 1, 1, 0, 'SYSTEM', NOW()); @@ -98,9 +99,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('保证金收取比例低于最低标准', '保证金比例低于客户品种最低保证金率时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 13, - 'Operator', '<', 'ThresholdType', 'Variable', - 'ThresholdVariableId', 52 + 'VariableId', 13, 'VariableName', '保证金比例', 'VariableType', 'Numeric', + 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), + 'ThresholdVariableId', 52, 'ThresholdVariableName', '客户品种最低保证金率', 'Unit', '%' )), 'trade.MarginRate < config.MinMarginRate', 1, 1, 0, 'SYSTEM', NOW()); @@ -111,9 +112,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('起息日早于当前日期', '合约起息日早于当前日期时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 2, - 'Operator', '<', 'ThresholdType', 'Variable', - 'ThresholdVariableId', 21 + 'VariableId', 2, 'VariableName', '合约起息日', 'VariableType', 'Date', + 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), + 'ThresholdVariableId', 21, 'ThresholdVariableName', '当前日期', 'Unit', JSON_TYPE(NULL) )), 'trade.StartDate < sys.CurrentDate', 1, 1, 0, 'SYSTEM', NOW()); @@ -124,8 +125,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('支付日为银行间交易日', '支付日为银行间交易日时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 38, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 38, 'VariableName', '支付日是否银行间交易日', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) )), 'calc.IsSettlementDateTradingDay == true', 1, 1, 0, 'SYSTEM', NOW()); @@ -136,8 +138,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('到期日为银行间交易日', '到期日为银行间交易日时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 36, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 36, 'VariableName', '到期日是否银行间交易日', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) )), 'calc.IsExerciseDateTradingDay == true', 1, 1, 0, 'SYSTEM', NOW()); @@ -148,8 +151,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('平仓日为银行间交易日', '平仓日为银行间交易日时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 37, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 37, 'VariableName', '平仓日是否银行间交易日', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) )), 'calc.IsUnwindDateTradingDay == true', 1, 1, 0, 'SYSTEM', NOW()); @@ -160,8 +164,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('合约期限超阈值', '合约期限超过阈值(默认365天)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 26, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 365 + 'VariableId', 26, 'VariableName', '合约期限', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 365, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '天' )), 'calc.MaturityDays > 365', 1, 1, 0, 'SYSTEM', NOW()); @@ -172,8 +177,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('债券类净价偏离', '期初净价与上一收盘日中债估值净价偏离度超阈值(默认5%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 45, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5 + 'VariableId', 45, 'VariableName', '期初净价偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'Math.Abs(swap_position.PosiNetNoFeePrice - market.CBValuationNetPrice) / market.CBValuationNetPrice * 100 > 5', 1, 1, 0, 'SYSTEM', NOW()); @@ -184,8 +190,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('债券类收益率偏离', '期初收益率与上一收盘日中债估值收益率偏离度超阈值(默认5%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 46, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5 + 'VariableId', 46, 'VariableName', '期初收益率偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'Math.Abs(trade.InitYtm - market.CBValuationYtm) / market.CBValuationYtm * 100 > 5', 1, 1, 0, 'SYSTEM', NOW()); @@ -196,8 +203,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('非债券类价格偏离', '期初价格与上一日收盘价偏离度超阈值(默认5%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 47, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5 + 'VariableId', 47, 'VariableName', '期初价格偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'Math.Abs(trade.SpotPrice - market.LastClosePrice) / market.LastClosePrice * 100 > 5', 1, 1, 0, 'SYSTEM', NOW()); @@ -208,8 +216,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('单一交易对手累计标的数量超阈值', '对手方累计标的数量超过阈值(默认10个)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 32, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 10 + 'VariableId', 32, 'VariableName', '对手方累计标的数量', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 10, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '个' )), 'calc.CounterpartyUnderlyingCount > 10', 1, 1, 0, 'SYSTEM', NOW()); @@ -221,12 +230,14 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr '多头方向支付固定端场景下,利息端利率与FR007偏离度超阈值(默认3%)时触发审批', JSON_ARRAY( JSON_OBJECT( - 'VariableId', 41, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 41, 'VariableName', '多空方向为多头', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) ), JSON_OBJECT( - 'VariableId', 48, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 3 + 'VariableId', 48, 'VariableName', '利息端利率与FR007偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 3, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' ) ), 'calc.IsLongDirection == true && Math.Abs(calc.InterestRate - market.FR007) / market.FR007 * 100 > 3', @@ -239,12 +250,14 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr '空头方向场景下,利息端利率与借贷加权费率偏离度超阈值(默认2%)时触发审批', JSON_ARRAY( JSON_OBJECT( - 'VariableId', 42, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 42, 'VariableName', '多空方向为空头', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) ), JSON_OBJECT( - 'VariableId', 49, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 2 + 'VariableId', 49, 'VariableName', '利息端利率与借贷加权费率偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 2, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' ) ), 'calc.IsShortDirection == true && Math.Abs(calc.InterestRate - market.BondLendingRate) / market.BondLendingRate * 100 > 2', @@ -256,8 +269,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('账户授权收支方向不匹配', '保证金收支方向与账户授权方向不匹配时禁止交易', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 43, - 'Operator', '是', 'ThresholdType', 'Fixed', 'Value', true + 'VariableId', 43, 'VariableName', '保证金收支方向为支付', 'VariableType', 'Boolean', + 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) )), 'calc.IsMarginPay == true', 1, 1, 0, 'SYSTEM', NOW()); @@ -268,8 +282,9 @@ INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr ('执行价偏离超阈值', '执行价与参考价格偏离度超阈值(默认5%)时触发审批', JSON_ARRAY(JSON_OBJECT( - 'VariableId', 50, - 'Operator', '>', 'ThresholdType', 'Fixed', 'Value', 5 + 'VariableId', 50, 'VariableName', '执行价偏离度', 'VariableType', 'Numeric', + 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, + 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' )), 'Math.Abs(trade.StrikePrice - market.ReferencePrice) / market.ReferencePrice * 100 > 5', 1, 1, 0, 'SYSTEM', NOW()); diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs b/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs similarity index 55% rename from YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs rename to YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs index 81facae9..5f313d70 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RuleCondition.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs @@ -1,11 +1,15 @@ namespace YLErp.Modules.RiskEngine.Dto { - public class RuleCondition + public class FormulaCondition { public long VariableId { get; set; } + public string VariableName { get; set; } + public string VariableType { get; set; } public string Operator { get; set; } public string ThresholdType { get; set; } public object Value { get; set; } public long? ThresholdVariableId { get; set; } + public string ThresholdVariableName { get; set; } + public string Unit { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs b/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs new file mode 100644 index 00000000..cd85d0d7 --- /dev/null +++ b/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace YLErp.Modules.RiskEngine.Dto +{ + public class FormulaDefinition + { + public string LogicOperator { get; set; } + public List Conditions { get; set; } + } +} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleSimpleItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleSimpleItem.cs deleted file mode 100644 index aea6a343..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleSimpleItem.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskRuleSimpleItem - { - public long Id { get; set; } - public string RuleName { get; set; } - public string RuleText { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index 8767bfc4..58c9f5e5 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -79,7 +79,6 @@ using YLErp.Modules.RiskEngine.Dto; GetRuleVersions(long) 获取规则版本历史(从审计日志 SnapshotData 提取版本号) BatchDeleteRules(List) 批量删除规则(逐条保护检查,单事务) GetRuleApplications(long) 获取规则关联的应用配置列表 - GetAllRuleList() 获取所有启用规则(轻量字段,供应用配置下拉) ── 应用配置管理(9 个) ─────────────────────────────────────────── QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(多条件筛选,内存关联规则名称) @@ -260,22 +259,25 @@ namespace YLErp.Modules.RiskEngine private void ValidateConditionJson(string conditionJson) { - List conditions; + FormulaDefinition formula; try { - conditions = JsonConvert.DeserializeObject>(conditionJson); + formula = JsonConvert.DeserializeObject(conditionJson); } catch { throw new ServiceException("公式表达式 JSON 格式不合法"); } - if (conditions == null || conditions.Count == 0) + if (formula.Conditions == null || formula.Conditions.Count == 0) throw new ServiceException("公式条件列表不能为空"); - for (int i = 0; i < conditions.Count; i++) + if (formula.LogicOperator != "AND") + throw new ServiceException("当前仅支持 AND 逻辑运算符"); + + for (int i = 0; i < formula.Conditions.Count; i++) { - var cond = conditions[i]; + var cond = formula.Conditions[i]; var condLabel = $"条件{i + 1}"; var variableDef = DbContext.glms_risk_variable @@ -287,7 +289,7 @@ namespace YLErp.Modules.RiskEngine if (!validOperators.Contains(cond.Operator)) throw new ServiceException($"{condLabel}:操作符 '{cond.Operator}' 不适用于{GetDataTypeName(variableDef.DataType)}类型变量"); - if (cond.ThresholdType == "Fixed") + if (cond.ThresholdType == "fixed") { if (cond.Value == null) throw new ServiceException($"{condLabel}:固定阈值不能为空"); @@ -302,7 +304,7 @@ namespace YLErp.Modules.RiskEngine throw new ServiceException($"{condLabel}:日期型变量的阈值必须为合法日期"); } } - else if (cond.ThresholdType == "Variable") + else if (cond.ThresholdType == "variable") { if (!cond.ThresholdVariableId.HasValue) throw new ServiceException($"{condLabel}:变量阈值引用的变量 ID 不能为空"); @@ -315,7 +317,7 @@ namespace YLErp.Modules.RiskEngine } else { - throw new ServiceException($"{condLabel}:阈值类型 '{cond.ThresholdType}' 不合法,仅支持 Fixed/Variable"); + throw new ServiceException($"{condLabel}:阈值类型 '{cond.ThresholdType}' 不合法,仅支持 fixed/variable"); } } } @@ -443,15 +445,23 @@ namespace YLErp.Modules.RiskEngine if (ruleName.Length > 200) throw new ServiceException("规则名称长度不能超过200字符"); - if (string.IsNullOrWhiteSpace(ruleExpr)) - throw new ServiceException("规则表达式不能为空"); + bool hasStructured = !string.IsNullOrWhiteSpace(conditionJson); + bool hasFreeText = !string.IsNullOrWhiteSpace(ruleExpr); - if (!string.IsNullOrWhiteSpace(conditionJson)) + if (!hasStructured && !hasFreeText) + throw new ServiceException("公式表达式不能为空,请提供结构化条件或自由文本表达式"); + + if (hasStructured && hasFreeText) + throw new ServiceException("结构化条件和自由文本表达式不能同时提供,请选择其中一种模式"); + + if (hasStructured) { ValidateConditionJson(conditionJson); } - - ValidateRuleExpr(ruleExpr); + else + { + ValidateRuleExpr(ruleExpr); + } } private List ParseRuleIds(string ruleIds) @@ -833,20 +843,6 @@ namespace YLErp.Modules.RiskEngine return result; } - public List GetAllRuleList() - { - return DbContext.glms_risk_rule - .Where(r => r.Status == RiskRuleStatus.Active) - .OrderBy(r => r.id) - .Select(r => new RiskRuleSimpleItem - { - Id = r.id, - RuleName = r.RuleName, - RuleText = r.RuleText - }) - .ToList(); - } - #endregion #region Application Management @@ -1081,7 +1077,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), + WriteAuditLog("APPLICATION_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), $"修改应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}"); DbContext.SaveChanges(); @@ -1136,7 +1132,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_DISABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "停用应用配置"); + WriteAuditLog("APPLICATION_DISABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "停用应用配置"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1163,7 +1159,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_BATCH_ENABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量启用应用配置"); + WriteAuditLog("APPLICATION_BATCH_ENABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量启用应用配置"); } DbContext.SaveChanges(); @@ -1362,7 +1358,7 @@ namespace YLErp.Modules.RiskEngine variable.UpdateOptName = UserName; variable.UpdateDate = DateTime.Now; - WriteAuditLog("VAR_UPDATE", "VARIABLE", variableId, req.VariableName, $"修改变量:{variable.VariableName}"); + WriteAuditLog("VARIABLE_UPDATE", "VARIABLE", variableId, req.VariableName, $"修改变量:{variable.VariableName}"); DbContext.SaveChanges(); InvalidateVariableCache(); diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs index 9c97be3e..d65997dc 100644 --- a/YLErpWeb/Controllers/RiskRuleController.cs +++ b/YLErpWeb/Controllers/RiskRuleController.cs @@ -221,27 +221,6 @@ namespace YLErp.Web.Controllers } } - [HttpGet("risk-rules/list")] - [MyAuthorize("风控规则查看")] - public JsonResult GetAllRiskRules() - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetAllRuleList(); - 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 Application Management From 4ac18c4810a75ff787c5767d3c19274c8abca1d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:05:42 +0800 Subject: [PATCH 12/17] =?UTF-8?q?Revert=20"feat(risk-engine):=20=E5=AE=8C?= =?UTF-8?q?=E5=96=84=E9=A3=8E=E6=8E=A7=E8=A7=84=E5=88=99=E7=A7=8D=E5=AD=90?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=B8=8E=20CRUD=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E4=BC=98=E5=8C=96"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit c2fc699e5757202a0b9abd310e2ceb350d2f153c. --- .../DbUpdate/Ver-5.6.0/seed_applications.sql | 135 -------- .../DbUpdate/Ver-5.6.0/seed_rules.sql | 306 ------------------ .../DbUpdate/Ver-5.6.0/seed_variables.sql | 14 - .../RiskEngine/Dto/QueryRiskApplicationReq.cs | 2 +- .../RiskEngine/Dto/QueryRiskVariableReq.cs | 2 +- .../RiskEngine/Dto/RiskRuleListItem.cs | 2 - .../Dto/UpdateRiskApplicationReq.cs | 2 +- .../RiskEngine/Dto/UpdateRiskRuleReq.cs | 2 +- .../RiskEngine/Dto/UpdateRiskVariableReq.cs | 2 +- .../Modules/RiskEngine/RiskEngineService.cs | 2 +- .../Modules/RiskEngine/RiskRuleService.cs | 42 ++- 11 files changed, 31 insertions(+), 480 deletions(-) delete mode 100644 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql delete mode 100644 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql deleted file mode 100644 index 8c3cf942..00000000 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_applications.sql +++ /dev/null @@ -1,135 +0,0 @@ --- ============================================================ --- 规则应用初始数据(对应设计文档 §4.9.1 通用规则预置参考) --- 依赖:seed_rules.sql(规则数据需先插入) --- --- 每条规则对应一条应用配置(1:1),定义"何时、对谁、怎么处理" --- ControlStrategy: 1=Block(禁止), 2=Approval(审批), 3=Warning(提示) --- TriggerPoints: BOOK_CONFIRM=簿记交易确认 --- ScopeIsGlobal: 1=全局适用, 0=按维度配置(Scope字段为空表示"全部") --- ============================================================ - --- 规则1:挂钩标的集中度超阈值(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的集中度超阈值'; - --- 规则2:挂钩标的到期日小于合约到期日(禁止,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '挂钩标的到期日小于合约到期日'; - --- 规则3:名义本金超阈值(审批,账户/合约类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '名义本金超阈值'; - --- 规则4:保证金支付比例超阈值(审批,账户/标的类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '保证金支付比例超阈值'; - --- 规则5:保证金利率偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '保证金利率偏离'; - --- 规则6:保证金收取比例低于最低标准(审批,账户/标的类型) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '保证金收取比例低于最低标准'; - --- 规则7:起息日早于当前日期(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '起息日早于当前日期'; - --- 规则8:支付日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '支付日为银行间交易日'; - --- 规则9:到期日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '到期日为银行间交易日'; - --- 规则10:平仓日为银行间交易日(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '平仓日为银行间交易日'; - --- 规则11:合约期限超阈值(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '合约期限超阈值'; - --- 规则12:债券类净价偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '债券类净价偏离'; - --- 规则13:债券类收益率偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '债券类收益率偏离'; - --- 规则14:非债券类价格偏离(审批,全局) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '非债券类价格偏离'; - --- 规则15:单一交易对手累计标的数量超阈值(审批,对手方) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '单一交易对手累计标的数量超阈值'; - --- 规则16:多头支付固定端利率偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '多头支付固定端利率偏离'; - --- 规则17:空头利率减点借贷加权偏离(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '空头利率减点借贷加权偏离'; - --- 规则18:账户授权收支方向不匹配(禁止,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 1, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '账户授权收支方向不匹配'; - --- 规则19:执行价偏离超阈值(审批,账户) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 2, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '执行价偏离超阈值'; - --- 规则20:希腊字母限额超阈值(提示,账户/标的,预留接口) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', 0, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '希腊字母限额超阈值'; - --- 规则21:接近/触发敲入敲出价(提示,全局,预留接口) -INSERT INTO `glms_risk_rule_application` (`RuleIds`, `Status`, `ControlStrategy`, `TriggerPoints`, `ScopeIsGlobal`, `Version`, `OptId`, `OptName`, `OptDate`) -SELECT - CAST(r.id AS CHAR), 1, 3, 'BOOK_CONFIRM', 1, 1, 0, 'SYSTEM', NOW() -FROM `glms_risk_rule` r WHERE r.RuleName = '接近/触发敲入敲出价'; diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql deleted file mode 100644 index 33b72018..00000000 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_rules.sql +++ /dev/null @@ -1,306 +0,0 @@ --- ============================================================ --- 规则初始数据(对应设计文档 §4.9.1 通用规则预置参考) --- 依赖:seed_variables.sql(变量池数据需先插入,ID 1~44) --- --- 变量 ID 映射(seed_variables.sql 自增): --- 1=合约名义本金 2=合约起息日 3=合约到期日 4=合约平仓日 --- 5=合约支付日 6=期初净价 7=期初全价 8=期初收益率 --- 9=期初价格 10=期末全价 11=期末价格 12=保证金利率 --- 13=保证金比例 14=客户授信额度 --- 15=上一收盘日中债估值净价 16=上一收盘日中债估值全价 17=上一收盘日中债估值收益率 --- 18=上一日收盘价 19=借贷加权费率 20=FR007 21=当前日期 --- 22=挂钩标的到期日 23=标的发行余额 --- 24=挂钩标的集中度 25=授信占用率 26=合约期限 27=Delta --- 28=Gamma 29=Vega 30=Theta 31=利息端利率 --- 32=对手方累计标的数量 33=同一标的累计名义本金 34=同一客户累计名义本金 --- 35=总持仓名义本金 --- 36=到期日是否银行间交易日 37=平仓日是否银行间交易日 38=支付日是否银行间交易日 --- 39=利息端/浮动端方向是否同向 40=关键业务要素是否一致 --- 41=多空方向为多头 42=多空方向为空头 --- 43=保证金收支方向为支付 44=保证金收支方向为收取 --- 45=期初净价偏离度 46=期初收益率偏离度 47=期初价格偏离度 --- 48=利息端利率与FR007偏离度 49=利息端利率与借贷加权费率偏离度 --- 50=执行价偏离度 51=执行价 52=客户品种最低保证金率 53=参考价格 --- ============================================================ - - --- ============================================================ --- 插入 21 条通用规则 --- ============================================================ - --- 规则1:挂钩标的集中度超阈值(审批,全局) --- 条件:挂钩标的集中度(ID=24) > 30% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('挂钩标的集中度超阈值', - '挂钩标的集中度超过阈值(默认30%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 24, 'VariableName', '挂钩标的集中度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 30, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'calc.UnderlyingConcentration > 30', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则2:挂钩标的到期日小于合约到期日(禁止,全局) --- 条件:挂钩标的到期日(ID=22) < 合约到期日(ID=3) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('挂钩标的到期日小于合约到期日', - '挂钩标的到期日早于合约到期日时禁止交易', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 22, 'VariableName', '挂钩标的到期日', 'VariableType', 'Date', - 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), - 'ThresholdVariableId', 3, 'ThresholdVariableName', '合约到期日', 'Unit', JSON_TYPE(NULL) - )), - 'market.UnderlyingMaturityDate < trade.ExerciseDate', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则3:名义本金超阈值(审批,账户/合约类型) --- 条件:合约名义本金(ID=1) > 100000000(1亿元) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('名义本金超阈值', - '合约名义本金超过阈值(默认1亿元)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 1, 'VariableName', '合约名义本金', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 100000000, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '元' - )), - 'trade.StockEqvNotional > 100000000', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则4:保证金支付比例超阈值(审批,账户/标的类型) --- 条件:保证金比例(ID=13) > 50% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('保证金支付比例超阈值', - '保证金比例超过阈值(默认50%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 13, 'VariableName', '保证金比例', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 50, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'trade.MarginRate > 50', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则5:保证金利率偏离(审批,账户) --- 条件:保证金利率(ID=12) 不介于 [2%, 5%] -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('保证金利率偏离', - '保证金利率不在配置区间内(默认2%~5%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 12, 'VariableName', '保证金利率', 'VariableType', 'Numeric', - 'Operator', '不介于', 'ThresholdType', 'fixed', 'Value', JSON_ARRAY(2, 5), - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - '!(client_marginrate.InitMarginRebateRate >= 2 && client_marginrate.InitMarginRebateRate <= 5)', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则6:保证金收取比例低于最低标准(审批,账户/标的类型) --- 条件:保证金比例(ID=13) < 客户品种最低保证金率(ID=52) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('保证金收取比例低于最低标准', - '保证金比例低于客户品种最低保证金率时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 13, 'VariableName', '保证金比例', 'VariableType', 'Numeric', - 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), - 'ThresholdVariableId', 52, 'ThresholdVariableName', '客户品种最低保证金率', 'Unit', '%' - )), - 'trade.MarginRate < config.MinMarginRate', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则7:起息日早于当前日期(审批,全局) --- 条件:合约起息日(ID=2) < 当前日期(ID=21) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('起息日早于当前日期', - '合约起息日早于当前日期时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 2, 'VariableName', '合约起息日', 'VariableType', 'Date', - 'Operator', '<', 'ThresholdType', 'variable', 'Value', JSON_TYPE(NULL), - 'ThresholdVariableId', 21, 'ThresholdVariableName', '当前日期', 'Unit', JSON_TYPE(NULL) - )), - 'trade.StartDate < sys.CurrentDate', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则8:支付日为银行间交易日(审批,全局) --- 条件:支付日是否银行间交易日(ID=38) = 是 -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('支付日为银行间交易日', - '支付日为银行间交易日时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 38, 'VariableName', '支付日是否银行间交易日', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - )), - 'calc.IsSettlementDateTradingDay == true', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则9:到期日为银行间交易日(审批,全局) --- 条件:到期日是否银行间交易日(ID=36) = 是 -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('到期日为银行间交易日', - '到期日为银行间交易日时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 36, 'VariableName', '到期日是否银行间交易日', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - )), - 'calc.IsExerciseDateTradingDay == true', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则10:平仓日为银行间交易日(审批,全局) --- 条件:平仓日是否银行间交易日(ID=37) = 是 -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('平仓日为银行间交易日', - '平仓日为银行间交易日时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 37, 'VariableName', '平仓日是否银行间交易日', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - )), - 'calc.IsUnwindDateTradingDay == true', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则11:合约期限超阈值(审批,账户) --- 条件:合约期限(ID=26) > 365天 -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('合约期限超阈值', - '合约期限超过阈值(默认365天)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 26, 'VariableName', '合约期限', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 365, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '天' - )), - 'calc.MaturityDays > 365', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则12:债券类净价偏离(审批,全局) --- 条件:期初净价偏离度(ID=45) > 5% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('债券类净价偏离', - '期初净价与上一收盘日中债估值净价偏离度超阈值(默认5%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 45, 'VariableName', '期初净价偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'Math.Abs(swap_position.PosiNetNoFeePrice - market.CBValuationNetPrice) / market.CBValuationNetPrice * 100 > 5', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则13:债券类收益率偏离(审批,全局) --- 条件:期初收益率偏离度(ID=46) > 5% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('债券类收益率偏离', - '期初收益率与上一收盘日中债估值收益率偏离度超阈值(默认5%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 46, 'VariableName', '期初收益率偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'Math.Abs(trade.InitYtm - market.CBValuationYtm) / market.CBValuationYtm * 100 > 5', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则14:非债券类价格偏离(审批,全局) --- 条件:期初价格偏离度(ID=47) > 5% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('非债券类价格偏离', - '期初价格与上一日收盘价偏离度超阈值(默认5%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 47, 'VariableName', '期初价格偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'Math.Abs(trade.SpotPrice - market.LastClosePrice) / market.LastClosePrice * 100 > 5', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则15:单一交易对手累计标的数量超阈值(审批,对手方) --- 条件:对手方累计标的数量(ID=32) > 10个 -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('单一交易对手累计标的数量超阈值', - '对手方累计标的数量超过阈值(默认10个)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 32, 'VariableName', '对手方累计标的数量', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 10, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '个' - )), - 'calc.CounterpartyUnderlyingCount > 10', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则16:多头支付固定端利率偏离(审批,账户) --- 条件:多空方向为多头(ID=41) AND 利息端利率与FR007偏离度(ID=48) > 3% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('多头支付固定端利率偏离', - '多头方向支付固定端场景下,利息端利率与FR007偏离度超阈值(默认3%)时触发审批', - JSON_ARRAY( - JSON_OBJECT( - 'VariableId', 41, 'VariableName', '多空方向为多头', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - ), - JSON_OBJECT( - 'VariableId', 48, 'VariableName', '利息端利率与FR007偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 3, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - ) - ), - 'calc.IsLongDirection == true && Math.Abs(calc.InterestRate - market.FR007) / market.FR007 * 100 > 3', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则17:空头利率减点借贷加权偏离(审批,账户) --- 条件:多空方向为空头(ID=42) AND 利息端利率与借贷加权费率偏离度(ID=49) > 2% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('空头利率减点借贷加权偏离', - '空头方向场景下,利息端利率与借贷加权费率偏离度超阈值(默认2%)时触发审批', - JSON_ARRAY( - JSON_OBJECT( - 'VariableId', 42, 'VariableName', '多空方向为空头', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - ), - JSON_OBJECT( - 'VariableId', 49, 'VariableName', '利息端利率与借贷加权费率偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 2, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - ) - ), - 'calc.IsShortDirection == true && Math.Abs(calc.InterestRate - market.BondLendingRate) / market.BondLendingRate * 100 > 2', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则18:账户授权收支方向不匹配(禁止,账户) --- 条件:保证金收支方向为支付(ID=43)(实际需结合账户授权方向判断,此处简化) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('账户授权收支方向不匹配', - '保证金收支方向与账户授权方向不匹配时禁止交易', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 43, 'VariableName', '保证金收支方向为支付', 'VariableType', 'Boolean', - 'Operator', '是', 'ThresholdType', 'fixed', 'Value', true, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', JSON_TYPE(NULL) - )), - 'calc.IsMarginPay == true', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则19:执行价偏离超阈值(审批,账户) --- 条件:执行价偏离度(ID=50) > 5% -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('执行价偏离超阈值', - '执行价与参考价格偏离度超阈值(默认5%)时触发审批', - JSON_ARRAY(JSON_OBJECT( - 'VariableId', 50, 'VariableName', '执行价偏离度', 'VariableType', 'Numeric', - 'Operator', '>', 'ThresholdType', 'fixed', 'Value', 5, - 'ThresholdVariableId', JSON_TYPE(NULL), 'ThresholdVariableName', JSON_TYPE(NULL), 'Unit', '%' - )), - 'Math.Abs(trade.StrikePrice - market.ReferencePrice) / market.ReferencePrice * 100 > 5', - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则20:希腊字母限额超阈值(提示,预留接口,一期不纳入) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('希腊字母限额超阈值', - 'Delta/Gamma/Vega/Theta任一超阈值时提示(预留接口,一期不纳入)', - NULL, - NULL, - 1, 1, 0, 'SYSTEM', NOW()); - --- 规则21:接近/触发敲入敲出价(提示,预留接口,需确认具体判断逻辑) -INSERT INTO `glms_risk_rule` (`RuleName`, `RuleText`, `ConditionJson`, `RuleExpr`, `Status`, `Version`, `OptId`, `OptName`, `OptDate`) VALUES -('接近/触发敲入敲出价', - '标的价格接近敲入/敲出价时提示(预留接口,需确认具体判断逻辑)', - NULL, - NULL, - 1, 1, 0, 'SYSTEM', NOW()); diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql index 934a3a27..050d5470 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql @@ -45,20 +45,6 @@ INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit` ('同一标的累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该标的所有存续交易的 StockEqvNotional),含本笔', 'calc.SameUnderlyingTotalNotional', 310, 0, 'SYSTEM', NOW()), ('同一客户累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该客户所有存续交易的 StockEqvNotional),含本笔', 'calc.SameClientTotalNotional', 311, 0, 'SYSTEM', NOW()), ('总持仓名义本金', 3, 1, '元', '≥ 0', 'SUM(所有存续交易的 StockEqvNotional)', 'calc.TotalPositionNotional', 312, 0, 'SYSTEM', NOW()); --- 新增变量(ID 45~53): --- 45=期初净价偏离度 46=期初收益率偏离度 47=期初价格偏离度 --- 48=利息端利率与FR007偏离度 49=利息端利率与借贷加权费率偏离度 --- 50=执行价偏离度 51=执行价 52=客户品种最低保证金率 53=参考价格 -INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES -('期初净价偏离度', 3, 1, '%', '≥ 0', 'ABS(期初净价-中债估值净价)/中债估值净价*100', 'Math.Abs(swap_position.PosiNetNoFeePrice - market.CBValuationNetPrice) / market.CBValuationNetPrice * 100', 313, 0, 'SYSTEM', NOW()), -('期初收益率偏离度', 3, 1, '%', '≥ 0', 'ABS(期初收益率-中债估值收益率)/中债估值收益率*100', 'Math.Abs(trade.InitYtm - market.CBValuationYtm) / market.CBValuationYtm * 100', 314, 0, 'SYSTEM', NOW()), -('期初价格偏离度', 3, 1, '%', '≥ 0', 'ABS(期初价格-上一日收盘价)/上一日收盘价*100', 'Math.Abs(trade.SpotPrice - market.LastClosePrice) / market.LastClosePrice * 100', 315, 0, 'SYSTEM', NOW()), -('利息端利率与FR007偏离度', 3, 1, '%', '≥ 0', 'ABS(利息端利率-FR007)/FR007*100', 'Math.Abs(calc.InterestRate - market.FR007) / market.FR007 * 100', 316, 0, 'SYSTEM', NOW()), -('利息端利率与借贷加权费率偏离度', 3, 1, '%', '≥ 0', 'ABS(利息端利率-借贷加权费率)/借贷加权费率*100', 'Math.Abs(calc.InterestRate - market.BondLendingRate) / market.BondLendingRate * 100', 317, 0, 'SYSTEM', NOW()), -('执行价偏离度', 3, 1, '%', '≥ 0', 'ABS(执行价-参考价格)/参考价格*100', 'Math.Abs(trade.StrikePrice - market.ReferencePrice) / market.ReferencePrice * 100', 318, 0, 'SYSTEM', NOW()), -('执行价', 1, 1, '元', '≥ 0', 'trade.StrikePrice', 'trade.StrikePrice', 115, 0, 'SYSTEM', NOW()), -('客户品种最低保证金率', 1, 1, '%', '0~100', '客户品种最低保证金率(由账户/标的配置决定)', 'config.MinMarginRate', 116, 0, 'SYSTEM', NOW()), -('参考价格', 2, 1, '元', '≥ 0', '执行价参考价格(行情数据)', 'market.ReferencePrice', 210, 0, 'SYSTEM', NOW()); -- 4.4.4 布尔判断类(Category=4) INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs index bd494ed8..f195ee7c 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs @@ -5,7 +5,7 @@ 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 string TriggerPoint { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs index 639d9b3d..cc9dc45a 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs @@ -6,6 +6,6 @@ namespace YLErp.Modules.RiskEngine.Dto public class QueryRiskVariableReq : BaseSearchReq { public RiskVariableCategory? Category { get; set; } - public string VariableName { get; set; } + public string Keyword { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs index 988f55c4..ec0c3610 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs @@ -8,8 +8,6 @@ namespace YLErp.Modules.RiskEngine.Dto public long Id { get; set; } public string RuleName { get; set; } public string RuleText { get; set; } - public string ConditionJson { get; set; } - public string RuleExpr { get; set; } public RiskRuleStatus Status { get; set; } public int Version { get; set; } public string OptName { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs index b9af55a9..73cafe4f 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs @@ -12,6 +12,6 @@ namespace YLErp.Modules.RiskEngine.Dto public string ScopeUnderlyingTypes { get; set; } public string ScopeTradeTypes { get; set; } public bool ScopeIsGlobal { get; set; } - public int Version { get; set; } + public int ExpectedVersion { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs index 85521bac..466286c8 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs @@ -6,6 +6,6 @@ namespace YLErp.Modules.RiskEngine.Dto public string RuleText { get; set; } public string ConditionJson { get; set; } public string RuleExpr { get; set; } - public int Version { get; set; } + public int ExpectedVersion { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs index 2990b7c1..1c19502b 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs @@ -12,6 +12,6 @@ namespace YLErp.Modules.RiskEngine.Dto public string Description { get; set; } public string VariableExpr { get; set; } public int SortOrder { get; set; } - public int Version { get; set; } + public int ExpectedVersion { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 845149d3..9669c869 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -122,7 +122,7 @@ namespace YLErp.Modules.RiskEngine public static RiskEngineService GetInstance() => _instance.Value; - private RiskEngineService() : base(OptUserInfo.SystemUser) + private RiskEngineService() : base((OptUserInfo)null) { } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index 58c9f5e5..e12cdf1d 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -185,6 +185,7 @@ namespace YLErp.Modules.RiskEngine public class RiskRuleService : YLBaseService { private readonly IYcLogger _logger = LogFactory.GetLogger("RiskRuleService"); + private readonly RiskEngineService _riskEngineService; private HashSet _variableExprCache; private DateTime _variableCacheUpdateTime; @@ -207,14 +208,17 @@ namespace YLErp.Modules.RiskEngine 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 @@ -395,7 +399,7 @@ namespace YLErp.Modules.RiskEngine { try { - RiskEngineService.GetInstance().RefreshCache(); + _riskEngineService.RefreshCache(); } catch (Exception ex) { @@ -556,8 +560,6 @@ namespace YLErp.Modules.RiskEngine Id = r.id, RuleName = r.RuleName, RuleText = r.RuleText, - ConditionJson = r.ConditionJson, - RuleExpr = r.RuleExpr, Status = r.Status, Version = r.Version, OptName = r.OptName, @@ -651,7 +653,7 @@ namespace YLErp.Modules.RiskEngine { var rule = GetRuleOrThrow(ruleId); - if (rule.Version != req.Version) + if (rule.Version != req.ExpectedVersion) throw new ServiceException("规则已被其他用户修改,请重新加载后再编辑"); ValidateRuleParams(req.RuleName, req.ConditionJson, req.RuleExpr); @@ -702,11 +704,13 @@ namespace YLErp.Modules.RiskEngine 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, "启用规则"); + WriteAuditLog("RULE_ENABLE", "RULE", ruleId, rule.RuleName, "启用规则", + snapshotData: JsonConvert.SerializeObject(new { Version = rule.Version })); DbContext.SaveChanges(); TryRefreshCache(); @@ -719,6 +723,7 @@ 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; @@ -867,14 +872,6 @@ namespace YLErp.Modules.RiskEngine query = query.Where(a => a.TriggerPoints.Contains(req.TriggerPoint)); } - if (!string.IsNullOrWhiteSpace(req.RuleName)) - { - 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))); - } - var pagedApps = query.OrderByDescending(a => a.UpdateDate) .Select(a => new { @@ -940,6 +937,13 @@ namespace YLErp.Modules.RiskEngine }; }).ToList(); + if (!string.IsNullOrWhiteSpace(req.Keyword)) + { + resultList = resultList + .Where(a => a.RuleNames.Contains(req.Keyword)) + .ToList(); + } + return new SearchListResult { rows = resultList, @@ -1046,7 +1050,7 @@ namespace YLErp.Modules.RiskEngine { var app = GetApplicationOrThrow(applicationId); - if (app.Version != req.Version) + if (app.Version != req.ExpectedVersion) throw new ServiceException("应用配置已被其他用户修改,请重新加载后再编辑"); if (!string.IsNullOrWhiteSpace(req.RuleIds)) @@ -1111,6 +1115,7 @@ namespace YLErp.Modules.RiskEngine ValidateRuleIdsActive(app.RuleIds); app.Status = RiskRuleStatus.Active; + app.Version = app.Version + 1; app.UpdateOptId = UserId; app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; @@ -1128,6 +1133,7 @@ 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; @@ -1155,6 +1161,7 @@ namespace YLErp.Modules.RiskEngine foreach (var app in apps) { app.Status = RiskRuleStatus.Active; + app.Version = app.Version + 1; app.UpdateOptId = UserId; app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; @@ -1185,6 +1192,7 @@ namespace YLErp.Modules.RiskEngine foreach (var app in apps) { app.Status = RiskRuleStatus.Disabled; + app.Version = app.Version + 1; app.UpdateOptId = UserId; app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; @@ -1216,9 +1224,9 @@ namespace YLErp.Modules.RiskEngine query = query.Where(v => v.Category == req.Category.Value); } - if (!string.IsNullOrWhiteSpace(req.VariableName)) + if (!string.IsNullOrWhiteSpace(req.Keyword)) { - query = query.Where(v => v.VariableName.Contains(req.VariableName)); + query = query.Where(v => v.VariableName.Contains(req.Keyword)); } var result = query.OrderBy(v => v.SortOrder).ThenBy(v => v.VariableName) @@ -1326,7 +1334,7 @@ namespace YLErp.Modules.RiskEngine { var variable = GetVariableOrThrow(variableId); - if (variable.Version != req.Version) + if (variable.Version != req.ExpectedVersion) throw new ServiceException("变量已被其他用户修改,请重新加载后再编辑"); if (string.IsNullOrWhiteSpace(req.VariableName)) From d01253a5daeb272b6d460d905763ab965796160d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:13:41 +0800 Subject: [PATCH 13/17] =?UTF-8?q?Revert=20"feat:=20=E8=A7=84=E5=88=99?= =?UTF-8?q?=E5=8F=8C=E6=A8=A1=E5=BC=8F=E5=85=AC=E5=BC=8F=E8=A1=A8=E8=BE=BE?= =?UTF-8?q?=E5=BC=8F=EF=BC=88RuleExpr=EF=BC=89&=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E9=87=8D=E6=9E=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 28ed20e527e97eaf1b97e06f7c351d4779d89536. --- .../YLErp.Core/DBModels/glms_risk_rule.cs | 7 +- .../DBModels/glms_risk_rule_application.cs | 3 +- .../DBModels/glms_risk_rule_audit_log.cs | 1 + .../YLErp.Core/DBModels/glms_risk_variable.cs | 5 +- .../DbUpdate/Ver-5.6.0/prod.sql | 22 +- .../DbUpdate/Ver-5.6.0/seed_variables.sql | 59 -- .../RiskEngine/Compile/RuleCompiler.cs | 124 ++-- .../Dto/CreateRiskApplicationReq.cs | 2 +- .../RiskEngine/Dto/CreateRiskRuleReq.cs | 6 +- .../RiskEngine/Dto/CreateRiskVariableReq.cs | 5 +- .../RiskEngine/Dto/FormulaCondition.cs | 4 +- .../RiskEngine/Dto/QueryRiskRuleReq.cs | 2 +- .../RiskEngine/Dto/RiskApplicationDetail.cs | 8 +- .../RiskEngine/Dto/RiskApplicationListItem.cs | 6 +- .../RiskEngine/Dto/RiskAuditLogDetail.cs | 1 + .../RiskEngine/Dto/RiskAuditLogListItem.cs | 1 + .../Dto/RiskRuleApplicationSummary.cs | 1 + .../Modules/RiskEngine/Dto/RiskRuleDetail.cs | 7 +- .../RiskEngine/Dto/RiskRuleListItem.cs | 4 +- .../RiskEngine/Dto/RiskVariableDetail.cs | 5 +- .../RiskEngine/Dto/RiskVariableListItem.cs | 3 +- .../RiskEngine/Dto/RiskVariableSimpleItem.cs | 2 +- .../Dto/UpdateRiskApplicationReq.cs | 1 - .../RiskEngine/Dto/UpdateRiskRuleReq.cs | 6 +- .../RiskEngine/Dto/UpdateRiskVariableReq.cs | 4 +- .../Modules/RiskEngine/RiskEngineService.cs | 182 ++--- YLErpDAL/Modules/RiskEngine/RiskResult.cs | 4 +- YLErpDAL/Modules/RiskEngine/RiskRule.cs | 21 +- .../Modules/RiskEngine/RiskRuleService.cs | 650 ++++++++---------- YLErpWeb/App_Data/Config/otcformat.js | 125 +--- YLErpWeb/Common/BaseController.cs | 8 - 31 files changed, 512 insertions(+), 767 deletions(-) delete mode 100644 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule.cs index 1581b02f..cdb5896d 100644 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs +++ b/Framework/YLErp.Core/DBModels/glms_risk_rule.cs @@ -5,10 +5,11 @@ namespace YLErp.DBModels [Table("glms_risk_rule")] public class glms_risk_rule : DBModelWithOperator { + public string RuleCode { get; set; } public string RuleName { get; set; } - public string RuleText { get; set; } - public string ConditionJson { get; set; } - public string RuleExpr { get; set; } + public string RuleDescription { get; set; } + public string FormulaJson { get; set; } + public string FormulaText { get; set; } public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active; 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 aa5dbb6a..0c731e27 100644 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs +++ b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs @@ -5,7 +5,8 @@ namespace YLErp.DBModels [Table("glms_risk_rule_application")] public class glms_risk_rule_application : DBModelWithOperator { - public string RuleIds { get; set; } + public string ApplicationCode { get; set; } + public long RuleId { get; set; } public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active; public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs index fca43e2b..660927b0 100644 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs +++ b/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs @@ -9,6 +9,7 @@ namespace YLErp.DBModels public string TargetType { get; set; } public long TargetId { get; set; } public string TargetName { get; set; } + public string TargetCode { get; set; } public string OperationDetail { get; set; } public string Result { get; set; } public string SnapshotData { get; set; } diff --git a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs b/Framework/YLErp.Core/DBModels/glms_risk_variable.cs index 099312ae..8eff1191 100644 --- a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs +++ b/Framework/YLErp.Core/DBModels/glms_risk_variable.cs @@ -5,13 +5,16 @@ namespace YLErp.DBModels [Table("glms_risk_variable")] public class glms_risk_variable : DBModelWithOperator { + public string VariableCode { get; set; } public string VariableName { get; set; } public RiskVariableCategory Category { get; set; } public RiskVariableDataType DataType { get; set; } public string Unit { get; set; } public string ValueDomain { get; set; } public string Description { get; set; } - public string VariableExpr { get; set; } + public string ImplementationScript { get; set; } + public string SourceExpression { get; set; } + public bool IsImplemented { get; set; } public int Version { get; set; } = 1; public int SortOrder { get; set; } public int? UpdateOptId { get; set; } diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql index 3f266be1..8ba5327f 100644 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql +++ b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql @@ -2,10 +2,11 @@ SET FOREIGN_KEY_CHECKS=0; CREATE TABLE `glms_risk_rule` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `RuleCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则编码(RISK-YYYYMMDD-NNNN)', `RuleName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则名称', - `RuleText` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规则文本', - `ConditionJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '条件JSON(结构化模式, FormulaCondition数组)', - `RuleExpr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '规则表达式(自由文本模式, 类C#表达式)', + `RuleDescription` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规则描述', + `FormulaJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '公式JSON表达式', + `FormulaText` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '公式可读文本', `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted', `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', `OptId` int NULL DEFAULT NULL COMMENT '创建人Id', @@ -15,12 +16,14 @@ CREATE TABLE `glms_risk_rule` ( `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_rule_code`(`RuleCode` ASC) USING BTREE, INDEX `idx_status`(`Status` ASC) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则定义表' ROW_FORMAT = Dynamic; CREATE TABLE `glms_risk_rule_application` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', - `RuleIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '关联规则Id列表(逗号分隔)', + `ApplicationCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '应用编码(APP-YYYYMMDD-NNNN)', + `RuleId` bigint NOT NULL COMMENT '关联规则Id', `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted', `ControlStrategy` tinyint NOT NULL COMMENT '控制策略: 1=Block, 2=Approval, 3=Warning', `TriggerPoints` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '触发时点(逗号分隔)', @@ -37,6 +40,8 @@ CREATE TABLE `glms_risk_rule_application` ( `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_application_code`(`ApplicationCode` ASC) USING BTREE, + INDEX `idx_rule_id`(`RuleId` ASC) USING BTREE, INDEX `idx_status`(`Status` ASC) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则应用配置表' ROW_FORMAT = Dynamic; @@ -46,6 +51,7 @@ CREATE TABLE `glms_risk_rule_audit_log` ( `TargetType` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '目标类型(RULE/APPLICATION/VARIABLE)', `TargetId` bigint NOT NULL COMMENT '目标记录Id', `TargetName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '目标名称', + `TargetCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '目标编码', `OperationDetail` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '操作详情', `Result` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作结果', `SnapshotData` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '快照数据(JSON)', @@ -61,13 +67,16 @@ CREATE TABLE `glms_risk_rule_audit_log` ( CREATE TABLE `glms_risk_variable` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `VariableCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '变量编码', `VariableName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '变量名称', `Category` tinyint NOT NULL COMMENT '分类: 1=BookingElement, 2=MarketData, 3=SystemCalc, 4=BooleanCheck', `DataType` tinyint NOT NULL COMMENT '数据类型: 1=Numeric, 2=Date, 3=Boolean', `Unit` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '单位', `ValueDomain` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '值域描述', `Description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', - `VariableExpr` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '变量表达式(C#表达式)', + `ImplementationScript` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '实现脚本(C#表达式)', + `SourceExpression` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '来源表达式', + `IsImplemented` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已实现: 0=否, 1=是', `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', `SortOrder` int NOT NULL DEFAULT 0 COMMENT '排序', `OptId` int NULL DEFAULT NULL COMMENT '创建人Id', @@ -76,7 +85,8 @@ CREATE TABLE `glms_risk_variable` ( `UpdateOptId` int NULL DEFAULT NULL COMMENT '最后修改人Id', `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', - PRIMARY KEY (`id`) USING BTREE + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_variable_code`(`VariableCode` ASC) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控变量池定义表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS=1; diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql deleted file mode 100644 index 050d5470..00000000 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/seed_variables.sql +++ /dev/null @@ -1,59 +0,0 @@ --- ============================================================ --- 变量池初始数据(对应设计文档 §4.4 变量池完整清单) --- ============================================================ - --- 4.4.1 簿记要素类(Category=1) -INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES -('合约名义本金', 1, 1, '元', '≥ 0', 'trade.StockEqvNotional 或 swap_position.PosiNotionalValue', 'trade.StockEqvNotional', 101, 0, 'SYSTEM', NOW()), -('合约起息日', 1, 2, NULL, NULL, 'trade.StartDate 或 swap_position.PosiStartDate', 'trade.StartDate', 102, 0, 'SYSTEM', NOW()), -('合约到期日', 1, 2, NULL, NULL, 'trade.ExerciseDate 或 swap_position.PosiMatuirityDate', 'trade.ExerciseDate', 103, 0, 'SYSTEM', NOW()), -('合约平仓日', 1, 2, NULL, NULL, 'trade.UnWindDate', 'trade.UnWindDate', 104, 0, 'SYSTEM', NOW()), -('合约支付日', 1, 2, NULL, NULL, 'trade.SettlementDate', 'trade.SettlementDate', 105, 0, 'SYSTEM', NOW()), -('期初净价', 1, 1, '元', '≥ 0', 'swap_position.PosiNetNoFeePrice(债券 TRS)', 'swap_position.PosiNetNoFeePrice', 106, 0, 'SYSTEM', NOW()), -('期初全价', 1, 1, '元', '≥ 0', 'swap_position.PosiNetFeePrice(债券 TRS)/ trade.SpotPrice(其他)', 'swap_position.PosiNetFeePrice', 107, 0, 'SYSTEM', NOW()), -('期初收益率', 1, 1, '%', NULL, 'trade.InitYtm 或 swap_position.InitYtm', 'trade.InitYtm', 108, 0, 'SYSTEM', NOW()), -('期初价格', 1, 1, '元', '≥ 0', 'trade.SpotPrice(非债券类)', 'trade.SpotPrice', 109, 0, 'SYSTEM', NOW()), -('期末全价', 1, 1, '元', '≥ 0', 'eod_swap_position.UnderlyingPrice(债券 TRS)', 'eod_swap_position.UnderlyingPrice', 110, 0, 'SYSTEM', NOW()), -('期末价格', 1, 1, '元', '≥ 0', 'trade.FinalPrice', 'trade.FinalPrice', 111, 0, 'SYSTEM', NOW()), -('保证金利率', 1, 1, '%', NULL, 'client_marginrate.InitMarginRebateRate', 'client_marginrate.InitMarginRebateRate', 112, 0, 'SYSTEM', NOW()), -('保证金比例', 1, 1, '%', '0~100', 'trade.MarginRate', 'trade.MarginRate', 113, 0, 'SYSTEM', NOW()), -('客户授信额度', 1, 1, '元', '≥ 0', 'credit.Credit', 'credit.Credit', 114, 0, 'SYSTEM', NOW()); - --- 4.4.2 行情类(Category=2,统一取上一交易日收盘价) -INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES -('上一收盘日中债估值净价', 2, 1, '元', '≥ 0', '资讯数据', 'market.CBValuationNetPrice', 201, 0, 'SYSTEM', NOW()), -('上一收盘日中债估值全价', 2, 1, '元', '≥ 0', '资讯数据', 'market.CBValuationFullPrice', 202, 0, 'SYSTEM', NOW()), -('上一收盘日中债估值收益率', 2, 1, '%', NULL, '资讯数据', 'market.CBValuationYtm', 203, 0, 'SYSTEM', NOW()), -('上一日收盘价', 2, 1, '元', '≥ 0', '行情数据,按标的区分', 'market.LastClosePrice', 204, 0, 'SYSTEM', NOW()), -('借贷加权费率', 2, 1, '%', NULL, 'CMDM 标的债券借贷费率行情表', 'market.BondLendingRate', 205, 0, 'SYSTEM', NOW()), -('FR007', 2, 1, '%', NULL, '上一交易日收盘价', 'market.FR007', 206, 0, 'SYSTEM', NOW()), -('当前日期', 2, 2, NULL, NULL, 'DateTime.Today', 'sys.CurrentDate', 207, 0, 'SYSTEM', NOW()), -('挂钩标的到期日', 2, 2, NULL, NULL, '资讯数据', 'market.UnderlyingMaturityDate', 208, 0, 'SYSTEM', NOW()), -('标的发行余额', 2, 1, '元', '≥ 0', '资讯数据', 'market.UnderlyingIssueBalance', 209, 0, 'SYSTEM', NOW()); - --- 4.4.3 系统计算值类(Category=3) -INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES -('挂钩标的集中度', 3, 1, '%', '0~100', '同一标的存续交易总名义本金 ÷ 标的发行余额 × 100', 'calc.UnderlyingConcentration', 301, 0, 'SYSTEM', NOW()), -('授信占用率', 3, 1, '%', '0~100', '(已占用授信 + 本笔授信占用) ÷ 授信总额 × 100', 'calc.CreditUsageRate', 302, 0, 'SYSTEM', NOW()), -('合约期限', 3, 1, '天', '≥ 0', '(ExerciseDate - StartDate).Days', 'calc.MaturityDays', 303, 0, 'SYSTEM', NOW()), -('Delta', 3, 1, NULL, NULL, 'realtime_trade_risk.Delta(预留接口,一期不纳入)', 'realtime_trade_risk.Delta', 304, 0, 'SYSTEM', NOW()), -('Gamma', 3, 1, NULL, NULL, 'realtime_trade_risk.Gamma(预留接口,一期不纳入)', 'realtime_trade_risk.Gamma', 305, 0, 'SYSTEM', NOW()), -('Vega', 3, 1, NULL, NULL, 'realtime_trade_risk.Vega(预留接口,一期不纳入)', 'realtime_trade_risk.Vega', 306, 0, 'SYSTEM', NOW()), -('Theta', 3, 1, NULL, NULL, 'realtime_trade_risk.Theta(预留接口,一期不纳入)', 'realtime_trade_risk.Theta', 307, 0, 'SYSTEM', NOW()), -('利息端利率', 3, 1, '%', NULL, '固定利率 或 FR007 ± 加点', 'calc.InterestRate', 308, 0, 'SYSTEM', NOW()), -('对手方累计标的数量', 3, 1, '个', '≥ 0', 'COUNT(DISTINCT UnderlyingId) 该对手方所有存续交易,含本笔', 'calc.CounterpartyUnderlyingCount', 309, 0, 'SYSTEM', NOW()), -('同一标的累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该标的所有存续交易的 StockEqvNotional),含本笔', 'calc.SameUnderlyingTotalNotional', 310, 0, 'SYSTEM', NOW()), -('同一客户累计名义本金', 3, 1, '元', '≥ 0', 'SUM(该客户所有存续交易的 StockEqvNotional),含本笔', 'calc.SameClientTotalNotional', 311, 0, 'SYSTEM', NOW()), -('总持仓名义本金', 3, 1, '元', '≥ 0', 'SUM(所有存续交易的 StockEqvNotional)', 'calc.TotalPositionNotional', 312, 0, 'SYSTEM', NOW()); - --- 4.4.4 布尔判断类(Category=4) -INSERT INTO `glms_risk_variable` (`VariableName`, `Category`, `DataType`, `Unit`, `ValueDomain`, `Description`, `VariableExpr`, `SortOrder`, `OptId`, `OptName`, `OptDate`) VALUES -('到期日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsExerciseDateTradingDay', 401, 0, 'SYSTEM', NOW()), -('平仓日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsUnwindDateTradingDay', 402, 0, 'SYSTEM', NOW()), -('支付日是否银行间交易日', 4, 3, NULL, NULL, '查询银行间交易日历', 'calc.IsSettlementDateTradingDay', 403, 0, 'SYSTEM', NOW()), -('利息端/浮动端方向是否同向', 4, 3, NULL, NULL, '利息端"收取"↔浮动端"多头",利息端"支付"↔浮动端"空头"', 'calc.IsInterestFloatSameDirection', 404, 0, 'SYSTEM', NOW()), -('关键业务要素是否一致', 4, 3, NULL, NULL, '交易确认书 vs 簿记要素(大模型方案)', 'calc.IsKeyElementsConsistent', 405, 0, 'SYSTEM', NOW()), -('多空方向为多头', 4, 3, NULL, NULL, 'trade.BuySell == "买入" 或浮动端为多头', 'calc.IsLongDirection', 406, 0, 'SYSTEM', NOW()), -('多空方向为空头', 4, 3, NULL, NULL, '与多头互斥', 'calc.IsShortDirection', 407, 0, 'SYSTEM', NOW()), -('保证金收支方向为支付', 4, 3, NULL, NULL, '保证金方向为支付', 'calc.IsMarginPay', 408, 0, 'SYSTEM', NOW()), -('保证金收支方向为收取', 4, 3, NULL, NULL, '与支付互斥', 'calc.IsMarginReceive', 409, 0, 'SYSTEM', NOW()); diff --git a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs index 0d2f778f..55115803 100644 --- a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs +++ b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs @@ -32,7 +32,8 @@ namespace YLErp.Modules.RiskEngine /// - 可调试:生成的脚本代码可直接阅读和理解 /// /// 示例: - /// RuleExpr: Convert.ToDecimal(((YLErp.DBModels.trade)DataMap["trade"]).StockEqvNotional) > 100000000m + /// FormulaJson: { "conditions": [{"variableCode":"trade.StockEqvNotional","operator":">","value":100000000}] } + /// 生成脚本:Convert.ToDecimal(((YLErp.DBModels.trade)DataMap["trade"]).StockEqvNotional) > 100000000m /// 编译结果:Func<RiskContext, bool>(内部通过 Roslyn 编译缓存) /// public static class RuleCompiler @@ -62,48 +63,51 @@ namespace YLErp.Modules.RiskEngine /// public static void Compile(this RiskRule rule) { - var result = ValidateAndCompileRule(rule); - if (!result.Success) + if (string.IsNullOrWhiteSpace(rule.FormulaJson)) { - throw new InvalidOperationException(result.ErrorMessage); + throw new ArgumentException("规则 FormulaJson 不能为空", nameof(rule.FormulaJson)); } + + // 1. 解析 FormulaJson + var conditions = ParseFormulaJson(rule.FormulaJson); + + // 2. 生成 Roslyn C# 脚本代码 + string scriptCode = BuildScriptCode(conditions); + + // 3. 编译脚本(只编译一次,后续直接执行) + var compiled = CompileScript(scriptCode); + + // 4. 写入规则(不持久化,仅内存缓存) + rule.CompiledScript = compiled; } /// - /// 校验并编译规则表达式。 - /// 当前 RuleExpr 要求是 Roslyn 可直接执行的 bool 表达式。 + /// 解析 FormulaJson,提取条件列表 /// - public static RuleCompileResult ValidateAndCompileFormula(string formulaExp) + private static List<(string variableCode, string op, object value, string variableType)> ParseFormulaJson(string formulaJson) { - if (string.IsNullOrWhiteSpace(formulaExp)) + var result = new List<(string, string, object, string)>(); + var jObj = JObject.Parse(formulaJson); + var conditions = jObj["conditions"] as JArray; + + if (conditions == null || !conditions.Any()) { - return RuleCompileResult.Fail("规则 RuleExpr 不能为空"); + throw new ArgumentException("FormulaJson 中缺少 conditions"); } try { - var compiled = CompileScript(formulaExp); - return RuleCompileResult.Ok(compiled); - } - catch (Exception ex) - { - return RuleCompileResult.Fail(ex.Message); - } - } + string variableCode = cond["variableCode"]?.Value(); + string op = cond["operator"]?.Value(); + object value = cond["value"]?.Value(); + string variableType = cond["variableType"]?.Value() ?? "numeric"; - /// - /// 校验并编译规则,编译成功后同时写入规则对象和内存缓存。 - /// - public static RuleCompileResult ValidateAndCompileRule(RiskRule rule) - { - if (rule == null) - { - return RuleCompileResult.Fail("规则不能为空"); - } - - if (rule.Id <= 0) - { - return RuleCompileResult.Fail("规则 Id 不能为空"); + if (string.IsNullOrWhiteSpace(variableCode) || string.IsNullOrWhiteSpace(op)) + { + throw new ArgumentException("条件中缺少 variableCode 或 operator"); + } + + result.Add((variableCode, op, value, variableType)); } var result = ValidateAndCompileFormula(rule.RuleExpr); @@ -118,53 +122,29 @@ namespace YLErp.Modules.RiskEngine } /// - /// 解析 ConditionJson,提取条件列表。 - /// 要求 expression 明确描述实际取值路径。 - /// 当前主执行链不再使用该方法,仅作为早期方案保留。 + /// 根据条件列表生成 Roslyn C# 脚本代码 + /// 多条件自动用 &&(AND)连接 /// - // private static List<(string variableCode, string expression, string op, object value, string variableType)> ParseConditionJson(string conditionJson) - // { - // var result = new List<(string, string, string, object, string)>(); - // var jObj = JObject.Parse(formulaJson); - // var conditions = jObj["conditions"] as JArray; - - // if (conditions == null || !conditions.Any()) - // { - // throw new ArgumentException("ConditionJson 中缺少 conditions"); - // } - - // foreach (var cond in conditions) - // { - // string variableCode = cond["variableCode"]?.Value(); - // string expression = cond["expression"]?.Value(); - // string op = cond["operator"]?.Value(); - // object value = cond["value"]?.Value(); - // string variableType = cond["variableType"]?.Value() ?? "numeric"; - - // if (string.IsNullOrWhiteSpace(expression) || string.IsNullOrWhiteSpace(op)) - // { - // throw new ArgumentException("条件中缺少 expression 或 operator"); - // } - - // result.Add((variableCode, expression, op, value, variableType)); - // } - - // return result; - // } - - /// - /// 根据条件列表生成 Roslyn C# 脚本代码。 - /// 多条件自动用 &&(AND)连接。 - /// 左值表达式统一基于 expression 构建。 - /// - private static string BuildScriptCode(List<(string variableCode, string expression, string op, object value, string variableType)> conditions) + private static string BuildScriptCode(List<(string variableCode, string op, object value, string variableType)> conditions) { var exprParts = new List(); - foreach (var (_, expression, op, value, variableType) in conditions) + foreach (var (variableCode, op, value, variableType) in conditions) { - // 先根据 expression 构建左值访问表达式,再拼接比较符和右值字面量 - string leftExpr = BuildMemberAccessExpression(expression, variableType); + // 解析 variableCode:前缀.属性名 + var parts = variableCode.Split('.'); + if (parts.Length != 2) + { + throw new ArgumentException($"variableCode 格式不正确:{variableCode}"); + } + + string prefix = parts[0].Trim().ToLower(); + string propertyName = parts[1].Trim(); + + if (!VariableTypeMap.TryGetValue(prefix, out string typeFullName)) + { + throw new ArgumentException($"未知前缀:{prefix}"); + } // 格式化阈值 string valueLiteral = FormatValueLiteral(value, variableType); diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs index f50d4f36..f1b80da9 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs @@ -4,7 +4,7 @@ namespace YLErp.Modules.RiskEngine.Dto { public class CreateRiskApplicationReq { - public string RuleIds { get; set; } + public long RuleId { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } public string ScopeAssetBookIds { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs index a6d69dda..ceceee6a 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs @@ -3,8 +3,8 @@ namespace YLErp.Modules.RiskEngine.Dto public class CreateRiskRuleReq { public string RuleName { get; set; } - public string RuleText { get; set; } - public string ConditionJson { get; set; } - public string RuleExpr { get; set; } + public string RuleDescription { get; set; } + public string FormulaJson { get; set; } + public string FormulaText { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs index 707db27b..5299a549 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs @@ -4,13 +4,16 @@ namespace YLErp.Modules.RiskEngine.Dto { public class CreateRiskVariableReq { + public string VariableCode { get; set; } public string VariableName { get; set; } public RiskVariableCategory Category { get; set; } public RiskVariableDataType DataType { get; set; } public string Unit { get; set; } public string ValueDomain { get; set; } public string Description { get; set; } - public string VariableExpr { get; set; } + public string ImplementationScript { get; set; } + public string SourceExpression { get; set; } + public bool IsImplemented { get; set; } public int SortOrder { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs b/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs index 5f313d70..af603b66 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs @@ -2,13 +2,13 @@ namespace YLErp.Modules.RiskEngine.Dto { public class FormulaCondition { - public long VariableId { get; set; } + public string VariableCode { get; set; } public string VariableName { get; set; } public string VariableType { get; set; } public string Operator { get; set; } public string ThresholdType { get; set; } public object Value { get; set; } - public long? ThresholdVariableId { get; set; } + public string ThresholdVariableCode { get; set; } public string ThresholdVariableName { get; set; } public string Unit { get; set; } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs index df7e1300..362316e4 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs @@ -7,6 +7,6 @@ namespace YLErp.Modules.RiskEngine.Dto { public string Keyword { get; set; } public RiskRuleStatus? Status { get; set; } - public long? VariableId { get; set; } + public string VariableCode { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs index fd9f4d77..a791dde8 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs @@ -6,10 +6,12 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskApplicationDetail { public long Id { get; set; } - public string RuleIds { get; set; } + public string ApplicationCode { get; set; } + public long RuleId { get; set; } + public string RuleCode { get; set; } public string RuleName { get; set; } - public string ConditionJson { get; set; } - public string RuleNames { get; set; } + public string FormulaJson { get; set; } + public string FormulaText { get; set; } public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs index 25bb7f2e..c04e7595 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs @@ -6,9 +6,11 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskApplicationListItem { public long Id { get; set; } - public string RuleIds { get; set; } + public string ApplicationCode { get; set; } + public long RuleId { get; set; } + public string RuleCode { get; set; } public string RuleName { get; set; } - public string RuleNames { get; set; } + public string FormulaText { get; set; } public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs index a4f52acf..ea08ebe8 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs @@ -9,6 +9,7 @@ namespace YLErp.Modules.RiskEngine.Dto public string TargetType { get; set; } public long TargetId { get; set; } public string TargetName { get; set; } + public string TargetCode { get; set; } public string OperationDetail { get; set; } public string Result { get; set; } public string SnapshotData { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs index 487f5f3f..aabc739a 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs @@ -9,6 +9,7 @@ namespace YLErp.Modules.RiskEngine.Dto public string TargetType { get; set; } public long TargetId { get; set; } public string TargetName { get; set; } + public string TargetCode { get; set; } public string OperationDetail { get; set; } public string Result { get; set; } public string OptName { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs index 23c6e0e2..3be19da8 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs @@ -5,6 +5,7 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskRuleApplicationSummary { public long Id { get; set; } + public string ApplicationCode { get; set; } public RiskRuleStatus Status { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs index 6abe752d..37251f3d 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs @@ -7,10 +7,11 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskRuleDetail { public long Id { get; set; } + public string RuleCode { get; set; } public string RuleName { get; set; } - public string RuleText { get; set; } - public string ConditionJson { get; set; } - public string RuleExpr { get; set; } + public string RuleDescription { get; set; } + public string FormulaJson { get; set; } + public string FormulaText { get; set; } public RiskRuleStatus Status { get; set; } public int Version { get; set; } public string OptName { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs index ec0c3610..509bfe33 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs @@ -6,8 +6,10 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskRuleListItem { public long Id { get; set; } + public string RuleCode { get; set; } public string RuleName { get; set; } - public string RuleText { get; set; } + public string RuleDescription { get; set; } + public string FormulaText { get; set; } public RiskRuleStatus Status { get; set; } public int Version { get; set; } public string OptName { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs index bbadd4ce..7470d12e 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs @@ -6,13 +6,16 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskVariableDetail { public long Id { get; set; } + public string VariableCode { get; set; } public string VariableName { get; set; } public RiskVariableCategory Category { get; set; } public RiskVariableDataType DataType { get; set; } public string Unit { get; set; } public string ValueDomain { get; set; } public string Description { get; set; } - public string VariableExpr { get; set; } + public string ImplementationScript { get; set; } + public string SourceExpression { get; set; } + public bool IsImplemented { get; set; } public int Version { get; set; } public int SortOrder { get; set; } public string OptName { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs index c4ddf647..56ab0beb 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs @@ -5,13 +5,14 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskVariableListItem { public long Id { get; set; } + public string VariableCode { get; set; } public string VariableName { get; set; } public RiskVariableCategory Category { get; set; } public RiskVariableDataType DataType { get; set; } public string Unit { get; set; } public string ValueDomain { get; set; } public string Description { get; set; } - public string VariableExpr { get; set; } + public bool IsImplemented { get; set; } public int Version { get; set; } public int SortOrder { get; set; } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs index 8585564d..a414930f 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs @@ -5,10 +5,10 @@ namespace YLErp.Modules.RiskEngine.Dto public class RiskVariableSimpleItem { public long Id { get; set; } + public string VariableCode { get; set; } public string VariableName { get; set; } public RiskVariableCategory Category { get; set; } public RiskVariableDataType DataType { get; set; } public string Unit { get; set; } - public string VariableExpr { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs index 73cafe4f..096f320e 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs @@ -4,7 +4,6 @@ namespace YLErp.Modules.RiskEngine.Dto { public class UpdateRiskApplicationReq { - public string RuleIds { get; set; } public RiskControlStrategy ControlStrategy { get; set; } public string TriggerPoints { get; set; } public string ScopeAssetBookIds { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs index 466286c8..1cbd7002 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs @@ -3,9 +3,9 @@ namespace YLErp.Modules.RiskEngine.Dto public class UpdateRiskRuleReq { public string RuleName { get; set; } - public string RuleText { get; set; } - public string ConditionJson { get; set; } - public string RuleExpr { get; set; } + public string RuleDescription { get; set; } + public string FormulaJson { get; set; } + public string FormulaText { get; set; } public int ExpectedVersion { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs index 1c19502b..f7db36bd 100644 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs +++ b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs @@ -10,7 +10,9 @@ namespace YLErp.Modules.RiskEngine.Dto public string Unit { get; set; } public string ValueDomain { get; set; } public string Description { get; set; } - public string VariableExpr { get; set; } + public string ImplementationScript { get; set; } + public string SourceExpression { get; set; } + public bool IsImplemented { get; set; } public int SortOrder { get; set; } public int ExpectedVersion { get; set; } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 9669c869..6c9311f8 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -17,15 +17,14 @@ using YLErp.Model; 【当前实现口径】(以本注释和实际代码为准) 核心概念: - - RiskRule(风控规则):规则定义本体,当前重点字段包括 Id、RuleName、 - RuleText、RuleExpr、Version、CompiledScript。 - - RiskRuleApplication(规则应用):与规则分离,承载启用状态、控制策略、触发时点、 - 适用范围(全局 / 账户 / 客户 / 标的类型 / 合约类型)。 - - RuleExpr:当前唯一主编译入口。要求内容是 Roslyn 可直接执行的 C# bool 表达式, - 例如直接访问 DataMap["trade"] 后做数值或日期比较。 - - ConditionJson:当前已在规则定义中保留,但不参与主执行链路。 - - RiskContext:一次风控检查的数据上下文,包含 TradeId、TriggerPoint 和 DataMap。 - - RuleCompiledCache:进程内编译结果缓存,按规则 Id 缓存 Func。 + - RiskRule(风控规则):单条规则包含完整的公式定义 + 校验策略 + 触发时点 + 适用范围。 + 不需要 "一个 Rule 对应多个 Application" 的复杂关系。 + - Content:用户输入的规则内容字符串,如"名义本金>1亿" + - 规则编译:在规则定义时(创建/加载/缓存刷新时)调用 RuleCompiler.Compile(rule), + 将 Content 解析并编译为 Func 委托,写入 rule.CompiledScript。 + 判风控时直接执行委托,零解析开销。 + - RiskContext:一次风控检查的数据上下文(DataMap 机制) + - RiskResult:风控检查结果(Passed / Blocked / NeedApproval / Warnings / TriggeredRules) 当前编译流程: 1. 外部准备好规则对象,在 RuleExpr 中直接写最终可执行表达式 @@ -43,11 +42,10 @@ using YLErp.Model; 6. 执行规则委托,按 ControlStrategy 聚合为 Blocked / NeedApproval / Warnings 7. 返回 RiskResult - 当前维度匹配规则: - - 全局:ScopeIsGlobal=true 时直接命中 - - 同一维度内多选:并集(OR) - - 不同维度之间:交集(AND) - - 某维度留空:视为该维度不限制 + 与旧设计文档的差异: + - 删除 RiskRuleApplication 模型,将策略/时点/范围合并到 RiskRule 中 + - 删除 FormulaJson 实时解析,改为 Content → 表达式树编译 + - 规则编译跟随规则生命周期(创建/加载时),判风控时零解析 【与早期方案的主要差异】 - 当前不是 Content 解析或表达式树主导,而是 RuleExpr 直编译主导 @@ -73,14 +71,25 @@ using YLErp.Model; 【当前进度】(截至 2026-06-24) ✅ 已完成: - 1. RiskEngine 第一版执行链路已跑通:QuotaMonitorService -> RiskEngineService -> RuleCompiler - 2. 风控上下文通过 DataMap 传入 trade 对象,支持规则脚本直接访问交易字段 - 3. RuleExpr 直编译方案已接入,支持数值比较和日期比较 - 4. Application 通用维度匹配已支持:全局 / 账户 / 客户 / 标的类型 / 合约类型 - 5. 维度组合逻辑已按文档确认:同维度 OR,不同维度 AND - 6. RuleCompiler 已支持校验 + 编译 + 写缓存 - 7. RuleCompiledCache / RuleCompileResult 已落地到 Compile 目录 - 8. RiskEngineService 已改为执行时优先从 RuleCompiledCache 读取委托 + 1. 创建 RiskEngine 文件夹,与 RiskModule 平级,实现代码隔离 + 2. 核心模型: + - RiskRule.cs:合并模型(公式 + 策略 + 时点 + 范围 + CompiledScript) + - RiskContext.cs:风控上下文(TradeId, TriggerPoint, DataMap) + - RiskResult.cs:风控结果(Passed, Blocked, NeedApproval, Warnings, TriggeredRules) + - RiskFormula.cs:FormulaJson 反序列化模型(辅助) + 3. RuleCompiler.cs: + - ParseContent:解析 Content 字符串(支持 > < >= <= = != ≠) + - ParseValue:解析数值(支持 万/亿/千/百分比) + - BuildExpression:表达式树构建(反射取值 + decimal 比较) + 4. RiskEngineService.EvaluateRisk: + - 加载示例规则列表 + - 筛选 TriggerPoint 匹配的规则 + - 调用 rule.CompiledScript(context) 执行预编译委托 + - 按 ControlStrategy 聚合结果(禁止/审批/提示) + 5. 集成到现有流程: + - QuotaMonitorService.QuotaCheck() 末尾已接入风控引擎 + - 触发时点:BOOK_CONFIRM(簿记交易确认) + 6. 编译验证通过 【待办事项 / TODO】 ⬜ 1. 接入真实规则来源 @@ -316,7 +325,22 @@ namespace YLErp.Modules.RiskEngine var applicationRuleIds = ParseRuleIds(application.RuleIds); if (!applicationRuleIds.Any()) { - _logger.Info($"[风控引擎] 应用未配置有效规则 - RuleIds: {application.RuleIds}"); + _logger.Info($"[风控引擎] 规则未编译,执行编译 - RuleCode: {rule.RuleCode}"); + RuleCompiler.Compile(rule); + } + + // 执行预编译委托 + bool triggered = false; + try + { + triggered = rule.CompiledScript(context); + _logger.Info($"[风控引擎] 规则执行 - RuleCode: {rule.RuleCode}, Triggered: {triggered}"); + } + catch (Exception ex) + { + _logger.Info($"[风控引擎] 规则执行异常 - RuleCode: {rule.RuleCode}, Error: {ex.Message}"); + result.HasError = true; + result.ErrorMessage += $"规则[{rule.RuleCode}]执行异常:{ex.Message};"; continue; } @@ -330,8 +354,7 @@ namespace YLErp.Modules.RiskEngine foreach (var missingRuleId in missingRuleIds) { - _logger.Info($"[风控引擎] 未找到对应规则定义 - RuleId: {missingRuleId}, ApplicationRuleIds: {application.RuleIds}"); - } + _logger.Info($"[风控引擎] 规则触发 - RuleCode: {rule.RuleCode}, Strategy: {rule.ControlStrategy}"); foreach (var rule in applicationRules) { @@ -342,17 +365,35 @@ namespace YLErp.Modules.RiskEngine // ------------------------------------------------------------ if (!RuleCompiledCache.TryGet(ruleId, out var compiledScript)) { - _logger.Info($"[风控引擎] 缓存中未命中已编译规则,执行兜底编译 - RuleId: {rule.Id}"); + case 1: // 禁止 + result.Blocked = true; + result.Passed = false; + result.TriggeredRules.Add(new TriggeredRuleInfo + { + RuleCode = rule.RuleCode, + RuleName = rule.RuleName, + ControlStrategy = "禁止", + FormulaText = rule.FormulaText, + Message = $"规则[{rule.RuleName}]触发:禁止" + }); + break; - var compileResult = RuleCompiler.ValidateAndCompileRule(rule); - if (!compileResult.Success) - { - _logger.Info($"[风控引擎] 规则编译失败 - RuleId: {rule.Id}, Error: {compileResult.ErrorMessage}"); - continue; - } + case 2: // 审批 + result.NeedApproval = true; + result.Passed = false; + result.TriggeredRules.Add(new TriggeredRuleInfo + { + RuleCode = rule.RuleCode, + RuleName = rule.RuleName, + ControlStrategy = "审批", + FormulaText = rule.FormulaText, + Message = $"规则[{rule.RuleName}]触发:需审批" + }); + break; - compiledScript = compileResult.CompiledScript; - } + case 3: // 提示 + result.Warnings.Add($"规则[{rule.RuleName}]触发:提示 - {rule.FormulaText}"); + break; // ------------------------------------------------------------ // 3.3 执行预编译委托 @@ -451,8 +492,9 @@ namespace YLErp.Modules.RiskEngine { RuleId = "ENGINE_ERROR", RuleName = "风控引擎执行异常", - RuleText = ex.Message, - Message = $"风控引擎异常:{ex.Message}" + ControlStrategy = "审批", + FormulaText = ex.Message, + Message = $"风控引擎异常:{ex.Message},需审批通过" }); _logger.Error($"[风控引擎] EvaluateRisk 异常 - TradeId: {context?.TradeId}, Error: {ex.Message}"); } @@ -465,59 +507,21 @@ namespace YLErp.Modules.RiskEngine /// private List LoadRulesFromDb() { - return DbContext.glms_risk_rule - .AsNoTracking() - .Where(r => r.Status != RiskRuleStatus.Deleted) - .OrderByDescending(r => r.UpdateDate ?? r.OptDate) - .Select(r => new RiskRule - { - Id = r.id, - RuleName = r.RuleName, - RuleText = r.RuleText, - ConditionJson = r.ConditionJson, - RuleExpr = r.RuleExpr, - Version = r.Version, - IsDeleted = r.Status == RiskRuleStatus.Deleted, - OptId = r.OptId ?? 0, - OptName = r.OptName, - OptDate = r.OptDate ?? DateTime.MinValue, - UpdateOptId = r.UpdateOptId ?? 0, - UpdateOptName = r.UpdateOptName, - UpdateDate = r.UpdateDate ?? r.OptDate ?? DateTime.MinValue - }) - .ToList(); - } - /// - /// 从数据库加载应用配置 - /// - private List LoadApplicationsFromDb() - { - return DbContext.glms_risk_rule_application - .AsNoTracking() - .Where(a => a.Status != RiskRuleStatus.Deleted) - .OrderByDescending(a => a.UpdateDate ?? a.OptDate) - .Select(a => new RiskRuleApplication - { - Id = a.id, - RuleIds = a.RuleIds, - Status = a.Status, - ControlStrategy = a.ControlStrategy, - TriggerPoints = a.TriggerPoints, - ScopeIsGlobal = a.ScopeIsGlobal, - ScopeAssetBookIds = a.ScopeAssetBookIds, - ScopeClientIds = a.ScopeClientIds, - ScopeUnderlyingTypes = a.ScopeUnderlyingTypes, - ScopeTradeTypes = a.ScopeTradeTypes, - Version = a.Version, - OptId = a.OptId ?? 0, - OptName = a.OptName, - OptDate = a.OptDate ?? DateTime.MinValue, - UpdateOptId = a.UpdateOptId ?? 0, - UpdateOptName = a.UpdateOptName, - UpdateDate = a.UpdateDate ?? a.OptDate ?? DateTime.MinValue - }) - .ToList(); - } + // 示例规则:多条件逗号分隔(自动转为 AND) + var rule = new RiskRule + { + Id = 103, + RuleCode = "RISK-20260622-0003", + RuleName = "测试规则:名义本金超过1亿且保证金比例超过50%禁止", + FormulaText = "名义本金>1亿, 保证金比例>50%", // 逗号分隔,解析时自动转为 AND + FormulaJson = "{ \"conditions\": [{\"variableCode\":\"trade.StockEqvNotional\",\"operator\":\">\",\"value\":100000000},{\"variableCode\":\"trade.MarginRate\",\"operator\":\">\",\"value\":0.5}] }", + Status = 1, // Active + ControlStrategy = 1, // 禁止 + TriggerPoints = "BOOK_CONFIRM", + ScopeIsGlobal = true, + Version = 1 + }; + RuleCompiler.Compile(rule); /// /// 判断应用配置是否命中当前交易。 diff --git a/YLErpDAL/Modules/RiskEngine/RiskResult.cs b/YLErpDAL/Modules/RiskEngine/RiskResult.cs index 69edbd57..7041ac18 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskResult.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskResult.cs @@ -40,8 +40,8 @@ namespace YLErp.Modules.RiskEngine { public string RuleId { get; set; } public string RuleName { get; set; } - public RiskControlStrategy ControlStrategy { get; set; } - public string RuleText { get; set; } + public string ControlStrategy { get; set; } + public string FormulaText { get; set; } public string Message { get; set; } } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRule.cs b/YLErpDAL/Modules/RiskEngine/RiskRule.cs index 5558c2b4..86b9b09b 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRule.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRule.cs @@ -11,21 +11,22 @@ namespace YLErp.Modules.RiskEngine { // === 基础信息 === public int Id { get; set; } + public string RuleCode { get; set; } public string RuleName { get; set; } - public string RuleText { get; set; } + public string Description { get; set; } /// - /// 【核心】条件JSON(结构化模式的条件列表存储),如 - /// {"conditions":[{"variableId":1,"operator":">","value":100000000}]} + /// 【核心】用户输入的规则内容字符串,如"名义本金>1亿" + /// 在规则创建/加载时由 RuleCompiler 解析并编译为 CompiledScript + /// + public string FormulaText { get; set; } + + /// + /// 【核心】公式 JSON(条件列表的结构化存储),如 + /// {"conditions":[{"variableCode":"trade.StockEqvNotional","operator":">","value":100000000}]} /// 由 RuleCompiler 解析并编译为 CompiledScript /// - public string ConditionJson { get; set; } - - /// - /// 【核心】规则表达式(自由文本模式,类C#表达式) - /// 由 RuleCompiler 编译为 CompiledScript - /// - public string RuleExpr { get; set; } + public string FormulaJson { get; set; } // === 生命周期 === public int Version { get; set; } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs index e12cdf1d..58ebe35d 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs @@ -22,14 +22,12 @@ using YLErp.Modules.RiskEngine.Dto; 【设计文档】 详细设计文档:.trae/documents/RiskRuleService详细设计.md(v1.1) - 需求来源文档:.trae/documents/风控引擎需求详细设计.md(v1.17) + 需求来源文档:.trae/documents/风控引擎需求详细设计.md(v1.14) 核心概念: - - Rule(规则):定义"什么情况下触发",支持两种公式模式(互斥): - · 结构化模式(ConditionJson 有值):变量 + 操作符 + 阈值/变量 的条件列表,前端拼接 RuleExpr - · 自由文本模式(RuleExpr 有值,ConditionJson 为空):前端直接编辑类 C# 表达式 - - Application(应用配置):定义"何时、对谁、怎么处理",关联多条规则(RuleIds 逗号分隔)+ 策略/时点/范围 - - Variable(变量):变量池中的可选项,用于构建规则条件(含取值表达式 VariableExpr) + - Rule(规则):定义"什么情况下触发",由变量 + 操作符 + 阈值组成的条件列表(FormulaJson) + - Application(应用配置):定义"何时、对谁、怎么处理",关联规则 + 策略/时点/范围(1个规则可配N个应用) + - Variable(变量):变量池中的可选项,用于构建规则条件(含实现脚本 ImplementationScript) - AuditLog(审计日志):记录所有 CRUD 操作,支持版本追溯(SnapshotData 存储版本号快照) 【架构位置】 @@ -47,8 +45,7 @@ using YLErp.Modules.RiskEngine.Dto; │ │ │ RiskRuleService(本文件) │ │ - 规则 / 应用 / 变量 / 审计日志的 CRUD │ - │ - ConditionJson 完整校验(结构化模式:变量存在性、操作符、类型匹配)│ - │ - RuleExpr 基础语法校验(自由文本模式:括号匹配、变量引用校验) │ + │ - FormulaJson 完整校验(变量存在性、操作符、类型匹配、阈值引用) │ │ - 乐观锁并发控制(Version 字段) │ │ - 删除保护(有 Active 引用时拒绝删除) │ │ - CRUD 后触发 RiskEngineService.RefreshCache() │ @@ -61,17 +58,17 @@ using YLErp.Modules.RiskEngine.Dto; ┌──────────────────────────▼──────────────────────────────────────┐ │ 数据访问层(EF Core DbContext 直接访问,无独立 Repository) │ │ - glms_risk_rule 规则定义表 │ - │ - glms_risk_rule_application 规则应用配置表(RuleIds 逗号分隔) │ + │ - glms_risk_rule_application 规则应用配置表(RuleId 外键) │ │ - glms_risk_variable 变量池定义表 │ │ - glms_risk_rule_audit_log 操作审计日志表 │ └─────────────────────────────────────────────────────────────────┘ -【方法清单】(共 27 个公开方法 + 9 个私有辅助方法) +【方法清单】(共 27 个公开方法 + 10 个私有辅助方法) ── 规则管理(10 个) ────────────────────────────────────────────── QueryRuleList(QueryRiskRuleReq) 查询规则列表(分页,含 ApplicationCount 子查询) GetRuleDetail(long) 获取规则详情(含关联应用摘要) - CreateRule(CreateRiskRuleReq) 新建规则(ConditionJson 完整校验) + CreateRule(CreateRiskRuleReq) 新建规则(RuleCode 自动生成,FormulaJson 完整校验) UpdateRule(long, UpdateRiskRuleReq) 修改规则(乐观锁校验,Version+1) DeleteRule(long) 删除规则(强保护:有 Active 应用时拒绝) EnableRule(long) 启用规则(仅 Disabled → Active) @@ -81,7 +78,7 @@ using YLErp.Modules.RiskEngine.Dto; GetRuleApplications(long) 获取规则关联的应用配置列表 ── 应用配置管理(9 个) ─────────────────────────────────────────── - QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(多条件筛选,内存关联规则名称) + QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(Join 规则表,多条件筛选) GetApplicationDetail(long) 获取应用详情(含关联规则信息) CreateApplication(CreateRiskApplicationReq) 新建应用(关联规则 Active 校验,触发时点/Scope 校验) UpdateApplication(long, UpdateRiskApplicationReq) 修改应用(乐观锁校验) @@ -94,7 +91,7 @@ using YLErp.Modules.RiskEngine.Dto; ── 变量管理(6 个) ─────────────────────────────────────────────── QueryVariableList(QueryRiskVariableReq) 查询变量列表(全量返回,Category/Keyword 筛选) GetVariableDetail(long) 获取变量详情(含 ReferenceCount 引用统计) - CreateVariable(CreateRiskVariableReq) 新建变量(VariableExpr 长度校验 ≤10000) + CreateVariable(CreateRiskVariableReq) 新建变量(编码唯一性校验,脚本长度校验 ≤10000) UpdateVariable(long, UpdateRiskVariableReq) 修改变量(乐观锁,DataType 变更保护) DeleteVariable(long) 删除变量(引用保护:有 Active 规则引用时拒绝,硬删除) GetAllVariableList() 获取所有已实现变量(轻量字段,供规则编辑器下拉) @@ -106,10 +103,10 @@ using YLErp.Modules.RiskEngine.Dto; ── 私有辅助方法 ─────────────────────────────────────────────────── WriteAuditLog(...) 写入审计日志(含 snapshotData 可选参数) - GetVariableExprSet() 获取变量表达式集合(5 分钟本地缓存) + GenerateCode(prefix, existingCodes) 编码生成(RISK/APP-YYYYMMDD-NNNN,每日上限 9999) + GetVariableCodeSet() 获取变量编码集合(5 分钟本地缓存) InvalidateVariableCache() 清除变量缓存 - ValidateConditionJson(conditionJson) ConditionJson 完整校验(结构化模式) - ValidateRuleExpr(ruleExpr) RuleExpr 基础语法校验(自由文本模式:括号匹配 + 变量引用校验) + ValidateFormulaJson(formulaJson) FormulaJson 完整校验 ValidateTriggerPoints(triggerPoints) 触发时点校验(5 个合法时点) ValidateScopeFields(req) Scope 字段校验(全局互斥 + ID 格式校验) ValidateIdList(idList, fieldName) ID 列表格式校验 @@ -121,26 +118,30 @@ using YLErp.Modules.RiskEngine.Dto; 2. 事务策略:Create* 方法使用 BeginTransaction 包裹双 SaveChanges(实体 + 审计日志同事务) 3. 并发控制:乐观锁(Version 字段),修改时版本不匹配抛 ServiceException 4. 删除保护:强保护模式,有 Active 引用时拒绝删除规则/变量 - 5. ConditionJson 校验:变量存在性(按 VariableId)+ 操作符合法性(按 DataType 映射)+ 类型匹配 + 阈值变量引用 - 6. RuleExpr:自由文本模式下前端直接编辑,后端做基础语法校验(括号匹配 + 变量引用校验) - 7. Scope 字段:逗号分隔字符串存储,全局适用时不允许指定其他维度 - 8. 缓存刷新:写操作后调用 TryRefreshCache(),失败仅记日志不影响操作结果 - 9. 批量操作:单事务批量,全部成功或全部失败 - 10. 变量缓存:5 分钟本地 HashSet 缓存,CRUD 后 InvalidateVariableCache() - 11. 审计日志:16 种操作类型,CRUD + 审计日志同一事务,SnapshotData 存储版本快照 - 12. 多规则支持:Application 通过 RuleIds(逗号分隔)关联多条规则,内存解析关联关系 + 5. RuleCode/ApplicationCode:Service 层自动生成,格式 RISK/APP-YYYYMMDD-NNNN + 6. FormulaJson 校验:变量存在性 + 操作符合法性(按 DataType 映射)+ 类型匹配 + 阈值变量引用 + 7. FormulaText:前端生成,后端仅校验非空并存储 + 8. Scope 字段:逗号分隔字符串存储,全局适用时不允许指定其他维度 + 9. 缓存刷新:写操作后调用 TryRefreshCache(),失败仅记日志不影响操作结果 + 10. 批量操作:单事务批量,全部成功或全部失败 + 11. 变量缓存:5 分钟本地 HashSet 缓存,CRUD 后 InvalidateVariableCache() + 12. 审计日志:16 种操作类型,CRUD + 审计日志同一事务,SnapshotData 存储版本快照 【数据库表】(MySQL,迁移脚本位于 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql) glms_risk_rule 规则定义表 + - 唯一索引:RuleCode - 普通索引:Status, OptDate - - 关键字段:ConditionJson(结构化条件列表), RuleExpr(类 C# 表达式,引擎执行用), Version(乐观锁) + - 关键字段:FormulaJson(公式定义), FormulaText(可读文本), Version(乐观锁) glms_risk_rule_application 规则应用配置表 - - 普通索引:Status, OptDate - - 关键字段:RuleIds(逗号分隔的规则 ID 列表), ControlStrategy(禁止/审批/提示), TriggerPoints(触发时点), Scope*(适用范围) + - 唯一索引:ApplicationCode + - 普通索引:RuleId, Status, OptDate + - 外键:RuleId → glms_risk_rule.id + - 关键字段:ControlStrategy(禁止/审批/提示), TriggerPoints(触发时点), Scope*(适用范围) glms_risk_variable 变量池定义表 - - 关键字段:VariableExpr(取值表达式), DataType(数值/日期/布尔) + - 唯一索引:VariableCode + - 关键字段:ImplementationScript(实现脚本), DataType(数值/日期/布尔), IsImplemented glms_risk_rule_audit_log 操作审计日志表 - 普通索引:TargetType+TargetId, OperationType, OptDate @@ -175,7 +176,7 @@ using YLErp.Modules.RiskEngine.Dto; 2. 所有写操作后必须调用 TryRefreshCache(),确保引擎缓存与数据库一致 3. 变量删除为硬删除(物理删除),规则/应用删除为软删除(Status=Deleted) 4. ExportAuditLogs 当前为占位实现,Excel 导出功能待后续迭代 - 5. VariableExpr 当前仅做基础长度校验(≤10000),完整编译校验待引入 Roslyn 库 + 5. ImplementationScript 当前仅做基础长度校验(≤10000),完整编译校验待引入 Roslyn 库 6. ValidateScopeFields 的 ID 存在性校验已跳过(前端下拉选择器保证有效性) ================================================================================ */ @@ -187,7 +188,7 @@ namespace YLErp.Modules.RiskEngine private readonly IYcLogger _logger = LogFactory.GetLogger("RiskRuleService"); private readonly RiskEngineService _riskEngineService; - private HashSet _variableExprCache; + private HashSet _variableCodeCache; private DateTime _variableCacheUpdateTime; private static readonly HashSet ValidTriggerPoints = new HashSet @@ -224,7 +225,7 @@ namespace YLErp.Modules.RiskEngine #region Private Helpers private void WriteAuditLog(string operationType, string targetType, long targetId, - string targetName, string operationDetail, string result = "成功", string snapshotData = null) + string targetName, string targetCode, string operationDetail, string result = "成功", string snapshotData = null) { var log = new glms_risk_rule_audit_log { @@ -232,6 +233,7 @@ namespace YLErp.Modules.RiskEngine TargetType = targetType, TargetId = targetId, TargetName = targetName, + TargetCode = targetCode, OperationDetail = operationDetail, Result = result, SnapshotData = snapshotData, @@ -242,31 +244,60 @@ namespace YLErp.Modules.RiskEngine DbContext.glms_risk_rule_audit_log.Add(log); } - private HashSet GetVariableExprSet() + private string GenerateCode(string prefix, IQueryable existingCodes) { - if (_variableExprCache == null || + 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 GetVariableCodeSet() + { + if (_variableCodeCache == null || DateTime.Now - _variableCacheUpdateTime > TimeSpan.FromMinutes(5)) { - _variableExprCache = DbContext.glms_risk_variable - .Where(v => v.VariableExpr != null && v.VariableExpr != "") - .Select(v => v.VariableExpr) + _variableCodeCache = DbContext.glms_risk_variable + .Where(v => v.IsImplemented) + .Select(v => v.VariableCode) .ToHashSet(); _variableCacheUpdateTime = DateTime.Now; } - return _variableExprCache; + return _variableCodeCache; } private void InvalidateVariableCache() { - _variableExprCache = null; + _variableCodeCache = null; } - private void ValidateConditionJson(string conditionJson) + private void ValidateFormulaJson(string formulaJson) { FormulaDefinition formula; try { - formula = JsonConvert.DeserializeObject(conditionJson); + formula = JsonConvert.DeserializeObject(formulaJson); } catch { @@ -279,15 +310,18 @@ namespace YLErp.Modules.RiskEngine 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.id == cond.VariableId); - if (variableDef == null) - throw new ServiceException($"{condLabel}:变量 ID '{cond.VariableId}' 不存在"); + .FirstOrDefault(v => v.VariableCode == cond.VariableCode); var validOperators = ValidOperatorsByType[variableDef.DataType]; if (!validOperators.Contains(cond.Operator)) @@ -310,12 +344,12 @@ namespace YLErp.Modules.RiskEngine } else if (cond.ThresholdType == "variable") { - if (!cond.ThresholdVariableId.HasValue) - throw new ServiceException($"{condLabel}:变量阈值引用的变量 ID 不能为空"); + 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.id == cond.ThresholdVariableId.Value); - if (thresholdVarDef == null) - throw new ServiceException($"{condLabel}:阈值变量 ID '{cond.ThresholdVariableId.Value}' 不存在"); + .FirstOrDefault(v => v.VariableCode == cond.ThresholdVariableCode); if (thresholdVarDef.DataType != variableDef.DataType) throw new ServiceException($"{condLabel}:阈值变量与条件变量的数据类型不一致"); } @@ -326,23 +360,6 @@ namespace YLErp.Modules.RiskEngine } } - private void ValidateRuleExpr(string ruleExpr) - { - if (ruleExpr.Length > 10000) - throw new ServiceException("公式表达式长度不能超过 10000 字符"); - - int parenDepth = 0; - foreach (char c in ruleExpr) - { - if (c == '(') parenDepth++; - else if (c == ')') parenDepth--; - if (parenDepth < 0) - throw new ServiceException("公式表达式括号不匹配:多余的右括号"); - } - if (parenDepth != 0) - throw new ServiceException("公式表达式括号不匹配:缺少右括号"); - } - private void ValidateTriggerPoints(string triggerPoints) { if (string.IsNullOrWhiteSpace(triggerPoints)) @@ -442,89 +459,17 @@ namespace YLErp.Modules.RiskEngine return variable; } - private void ValidateRuleParams(string ruleName, string conditionJson, string ruleExpr) + private void ValidateRuleParams(string ruleName, string formulaJson, string formulaText) { if (string.IsNullOrWhiteSpace(ruleName)) throw new ServiceException("规则名称不能为空"); if (ruleName.Length > 200) throw new ServiceException("规则名称长度不能超过200字符"); - - bool hasStructured = !string.IsNullOrWhiteSpace(conditionJson); - bool hasFreeText = !string.IsNullOrWhiteSpace(ruleExpr); - - if (!hasStructured && !hasFreeText) - throw new ServiceException("公式表达式不能为空,请提供结构化条件或自由文本表达式"); - - if (hasStructured && hasFreeText) - throw new ServiceException("结构化条件和自由文本表达式不能同时提供,请选择其中一种模式"); - - if (hasStructured) - { - ValidateConditionJson(conditionJson); - } - else - { - ValidateRuleExpr(ruleExpr); - } - } - - private List ParseRuleIds(string ruleIds) - { - if (string.IsNullOrWhiteSpace(ruleIds)) - throw new ServiceException("关联规则 ID 不能为空"); - - var ids = new List(); - foreach (var part in ruleIds.Split(',')) - { - if (long.TryParse(part.Trim(), out long id)) - ids.Add(id); - else - throw new ServiceException($"规则 ID 列表格式不合法:'{part.Trim()}'"); - } - - if (ids.Count == 0) - throw new ServiceException("关联规则 ID 不能为空"); - - return ids; - } - - private void ValidateRuleIdsActive(string ruleIds) - { - var ids = ParseRuleIds(ruleIds); - var rules = DbContext.glms_risk_rule - .Where(r => ids.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) - .ToList(); - - if (rules.Count != ids.Count) - throw new ServiceException("部分关联规则不存在或已删除"); - - foreach (var rule in rules) - { - if (rule.Status == RiskRuleStatus.Disabled) - throw new ServiceException($"关联规则 '{rule.RuleName}' 已停用,无法创建/启用应用配置"); - } - } - - private string ResolveRuleNames(string ruleIds) - { - if (string.IsNullOrWhiteSpace(ruleIds)) - return ""; - - var ids = ruleIds.Split(',') - .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) - .Where(id => id.HasValue) - .Select(id => id.Value) - .ToList(); - - if (ids.Count == 0) - return ""; - - var names = DbContext.glms_risk_rule - .Where(r => ids.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) - .Select(r => r.RuleName) - .ToList(); - - return string.Join(", ", names); + if (string.IsNullOrWhiteSpace(formulaJson)) + throw new ServiceException("公式表达式不能为空"); + if (string.IsNullOrWhiteSpace(formulaText)) + throw new ServiceException("公式可读文本不能为空"); + ValidateFormulaJson(formulaJson); } #endregion @@ -538,8 +483,9 @@ namespace YLErp.Modules.RiskEngine if (!string.IsNullOrWhiteSpace(req.Keyword)) { - query = query.Where(r => r.RuleName.Contains(req.Keyword) - || r.RuleText.Contains(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) @@ -547,26 +493,26 @@ namespace YLErp.Modules.RiskEngine query = query.Where(r => r.Status == req.Status.Value); } - if (req.VariableId.HasValue) + if (!string.IsNullOrWhiteSpace(req.VariableCode)) { - var varIdStr = req.VariableId.Value.ToString(); - query = query.Where(r => (r.ConditionJson != null && r.ConditionJson.Contains(varIdStr)) - || (r.RuleExpr != null && r.RuleExpr.Contains(varIdStr))); + 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, - RuleText = r.RuleText, + 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.RuleIds.Contains(r.id.ToString()) && a.Status != RiskRuleStatus.Deleted) + ApplicationCount = DbContext.glms_risk_rule_application.Count(a => a.RuleId == r.id && a.Status != RiskRuleStatus.Deleted) }); return result.ToSearchList(req); @@ -576,12 +522,12 @@ 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(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 @@ -591,10 +537,11 @@ namespace YLErp.Modules.RiskEngine return new RiskRuleDetail { Id = rule.id, + RuleCode = rule.RuleCode, RuleName = rule.RuleName, - RuleText = rule.RuleText, - ConditionJson = rule.ConditionJson, - RuleExpr = rule.RuleExpr, + RuleDescription = rule.RuleDescription, + FormulaJson = rule.FormulaJson, + FormulaText = rule.FormulaText, Status = rule.Status, Version = rule.Version, OptName = rule.OptName, @@ -607,14 +554,17 @@ namespace YLErp.Modules.RiskEngine public RiskRuleDetail CreateRule(CreateRiskRuleReq req) { - ValidateRuleParams(req.RuleName, req.ConditionJson, req.RuleExpr); + 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, - RuleText = req.RuleText, - ConditionJson = req.ConditionJson, - RuleExpr = req.RuleExpr, + RuleDescription = req.RuleDescription, + FormulaJson = req.FormulaJson, + FormulaText = req.FormulaText, Status = RiskRuleStatus.Active, Version = 1 }; @@ -631,7 +581,7 @@ namespace YLErp.Modules.RiskEngine DbContext.glms_risk_rule.Add(entity); DbContext.SaveChanges(); - WriteAuditLog("RULE_CREATE", "RULE", entity.id, req.RuleName, "创建规则", + WriteAuditLog("RULE_CREATE", "RULE", entity.id, req.RuleName, entity.RuleCode, "创建规则", snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version })); DbContext.SaveChanges(); @@ -656,18 +606,18 @@ namespace YLErp.Modules.RiskEngine if (rule.Version != req.ExpectedVersion) throw new ServiceException("规则已被其他用户修改,请重新加载后再编辑"); - ValidateRuleParams(req.RuleName, req.ConditionJson, req.RuleExpr); + ValidateRuleParams(req.RuleName, req.FormulaJson, req.FormulaText); rule.RuleName = req.RuleName; - rule.RuleText = req.RuleText; - rule.ConditionJson = req.ConditionJson; - rule.RuleExpr = req.RuleExpr; + 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, "修改规则"); + WriteAuditLog("RULE_UPDATE", "RULE", ruleId, req.RuleName, rule.RuleCode, "修改规则"); DbContext.SaveChanges(); TryRefreshCache(); @@ -679,9 +629,8 @@ 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); + .Count(a => a.RuleId == ruleId && a.Status == RiskRuleStatus.Active); if (activeCount > 0) throw new ServiceException($"该规则仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用"); @@ -691,7 +640,7 @@ namespace YLErp.Modules.RiskEngine rule.UpdateOptName = UserName; rule.UpdateDate = DateTime.Now; - WriteAuditLog("RULE_DELETE", "RULE", ruleId, rule.RuleName, "删除规则"); + WriteAuditLog("RULE_DELETE", "RULE", ruleId, rule.RuleName, rule.RuleCode, "删除规则"); DbContext.SaveChanges(); TryRefreshCache(); @@ -709,7 +658,7 @@ namespace YLErp.Modules.RiskEngine rule.UpdateOptName = UserName; rule.UpdateDate = DateTime.Now; - WriteAuditLog("RULE_ENABLE", "RULE", ruleId, rule.RuleName, "启用规则", + WriteAuditLog("RULE_ENABLE", "RULE", ruleId, rule.RuleName, rule.RuleCode, "启用规则", snapshotData: JsonConvert.SerializeObject(new { Version = rule.Version })); DbContext.SaveChanges(); @@ -728,7 +677,7 @@ namespace YLErp.Modules.RiskEngine rule.UpdateOptName = UserName; rule.UpdateDate = DateTime.Now; - WriteAuditLog("RULE_DISABLE", "RULE", ruleId, rule.RuleName, "停用规则"); + WriteAuditLog("RULE_DISABLE", "RULE", ruleId, rule.RuleName, rule.RuleCode, "停用规则"); DbContext.SaveChanges(); TryRefreshCache(); @@ -782,9 +731,8 @@ namespace YLErp.Modules.RiskEngine 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); + .Count(a => a.RuleId == rule.id && a.Status == RiskRuleStatus.Active); if (activeCount > 0) throw new ServiceException($"规则 '{rule.RuleName}' 仍有 {activeCount} 个生效中的应用配置,请先停用或删除相关应用"); } @@ -797,7 +745,7 @@ namespace YLErp.Modules.RiskEngine rule.UpdateOptName = UserName; rule.UpdateDate = DateTime.Now; - WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, "批量删除规则"); + WriteAuditLog("RULE_BATCH_DELETE", "RULE", rule.id, rule.RuleName, rule.RuleCode, "批量删除规则"); } DbContext.SaveChanges(); @@ -815,37 +763,34 @@ 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) - .OrderByDescending(a => a.UpdateDate) - .ToList(); + 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() + }; - var result = new List(); - foreach (var app in apps) - { - result.Add(new RiskApplicationListItem - { - Id = app.id, - RuleIds = app.RuleIds, - RuleNames = ResolveRuleNames(app.RuleIds), - 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; + return result.ToList(); } #endregion @@ -854,156 +799,114 @@ namespace YLErp.Modules.RiskEngine public SearchListResult QueryApplicationList(QueryRiskApplicationReq req) { - var query = DbContext.glms_risk_rule_application - .Where(a => a.Status != RiskRuleStatus.Deleted); + 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(a => a.Status == req.Status.Value); + query = query.Where(x => x.app.Status == req.Status.Value); } if (req.Strategy.HasValue) { - query = query.Where(a => a.ControlStrategy == req.Strategy.Value); + query = query.Where(x => x.app.ControlStrategy == req.Strategy.Value); } if (!string.IsNullOrWhiteSpace(req.TriggerPoint)) { - query = query.Where(a => a.TriggerPoints.Contains(req.TriggerPoint)); + query = query.Where(x => x.app.TriggerPoints.Contains(req.TriggerPoint)); } - var pagedApps = query.OrderByDescending(a => a.UpdateDate) - .Select(a => new + var result = query.OrderByDescending(x => x.app.UpdateDate) + .Select(x => new RiskApplicationListItem { - a.id, - a.RuleIds, - a.Status, - a.ControlStrategy, - a.TriggerPoints, - a.ScopeAssetBookIds, - a.ScopeClientIds, - a.ScopeUnderlyingTypes, - a.ScopeTradeTypes, - a.ScopeIsGlobal, - a.Version, - a.OptName, - a.OptDate, - a.UpdateOptName, - a.UpdateDate + 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() }); - var pagedResult = pagedApps.ToSearchList(req); - - var allRuleIds = pagedResult.rows - .SelectMany(a => a.RuleIds.Split(',')) - .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) - .Where(id => id.HasValue) - .Select(id => id.Value) - .Distinct() - .ToList(); - - var ruleMap = allRuleIds.Count > 0 - ? DbContext.glms_risk_rule - .Where(r => allRuleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) - .ToDictionary(r => (long)r.id, r => r.RuleName) - : new Dictionary(); - - var resultList = pagedResult.rows.Select(a => - { - var names = a.RuleIds.Split(',') - .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) - .Where(id => id.HasValue) - .Select(id => ruleMap.ContainsKey(id.Value) ? ruleMap[id.Value] : null) - .Where(n => n != null); - - return new RiskApplicationListItem - { - Id = a.id, - RuleIds = a.RuleIds, - RuleNames = string.Join(", ", names), - Status = a.Status, - ControlStrategy = a.ControlStrategy, - TriggerPoints = a.TriggerPoints, - ScopeAssetBookIds = a.ScopeAssetBookIds, - ScopeClientIds = a.ScopeClientIds, - ScopeUnderlyingTypes = a.ScopeUnderlyingTypes, - ScopeTradeTypes = a.ScopeTradeTypes, - ScopeIsGlobal = a.ScopeIsGlobal, - Version = a.Version, - OptName = a.OptName, - OptDate = a.OptDate.GetValueOrDefault(), - UpdateOptName = a.UpdateOptName, - UpdateDate = a.UpdateDate ?? a.OptDate.GetValueOrDefault() - }; - }).ToList(); - - if (!string.IsNullOrWhiteSpace(req.Keyword)) - { - resultList = resultList - .Where(a => a.RuleNames.Contains(req.Keyword)) - .ToList(); - } - - return new SearchListResult - { - rows = resultList, - records = pagedResult.records, - total = pagedResult.total, - page = pagedResult.page - }; + return result.ToSearchList(req); } public RiskApplicationDetail GetApplicationDetail(long applicationId) { - var app = DbContext.glms_risk_rule_application - .FirstOrDefault(a => a.id == applicationId && a.Status != RiskRuleStatus.Deleted); - if (app == null) + 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("应用配置不存在或已删除"); - var ruleIds = app.RuleIds.Split(',') - .Select(s => long.TryParse(s.Trim(), out long id) ? id : (long?)null) - .Where(id => id.HasValue) - .Select(id => id.Value) - .ToList(); - - var rules = DbContext.glms_risk_rule - .Where(r => ruleIds.Contains(r.id) && r.Status != RiskRuleStatus.Deleted) - .ToList(); - - var ruleNames = string.Join(", ", rules.Select(r => r.RuleName)); - var firstRule = rules.FirstOrDefault(); - - return new RiskApplicationDetail - { - Id = app.id, - RuleIds = app.RuleIds, - RuleNames = ruleNames, - ConditionJson = firstRule?.ConditionJson, - 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 detail; } public RiskApplicationDetail CreateApplication(CreateRiskApplicationReq req) { - ValidateRuleIdsActive(req.RuleIds); + 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 { - RuleIds = req.RuleIds, + ApplicationCode = applicationCode, + RuleId = req.RuleId, Status = RiskRuleStatus.Active, ControlStrategy = req.ControlStrategy, TriggerPoints = req.TriggerPoints, @@ -1027,7 +930,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, entity.ApplicationCode, entity.ApplicationCode, $"创建应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}", snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version })); DbContext.SaveChanges(); @@ -1053,9 +956,6 @@ namespace YLErp.Modules.RiskEngine if (app.Version != req.ExpectedVersion) throw new ServiceException("应用配置已被其他用户修改,请重新加载后再编辑"); - if (!string.IsNullOrWhiteSpace(req.RuleIds)) - ValidateRuleIdsActive(req.RuleIds); - ValidateTriggerPoints(req.TriggerPoints); var scopeReq = new CreateRiskApplicationReq { @@ -1067,8 +967,6 @@ namespace YLErp.Modules.RiskEngine }; ValidateScopeFields(scopeReq); - if (!string.IsNullOrWhiteSpace(req.RuleIds)) - app.RuleIds = req.RuleIds; app.ControlStrategy = req.ControlStrategy; app.TriggerPoints = req.TriggerPoints; app.ScopeAssetBookIds = req.ScopeAssetBookIds; @@ -1081,7 +979,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APPLICATION_UPDATE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), + WriteAuditLog("APPLICATION_UPDATE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, $"修改应用配置:策略={req.ControlStrategy}, 触发时点={req.TriggerPoints}"); DbContext.SaveChanges(); @@ -1100,7 +998,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APPLICATION_DELETE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "删除应用配置"); + WriteAuditLog("APPLICATION_DELETE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "删除应用配置"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1112,7 +1010,11 @@ namespace YLErp.Modules.RiskEngine if (app == null) throw new ServiceException("仅已停用的应用配置可以启用"); - ValidateRuleIdsActive(app.RuleIds); + 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; @@ -1120,7 +1022,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APP_ENABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "启用应用配置"); + WriteAuditLog("APP_ENABLE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "启用应用配置"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1138,7 +1040,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APPLICATION_DISABLE", "APPLICATION", applicationId, ResolveRuleNames(app.RuleIds), "停用应用配置"); + WriteAuditLog("APPLICATION_DISABLE", "APPLICATION", applicationId, app.ApplicationCode, app.ApplicationCode, "停用应用配置"); DbContext.SaveChanges(); TryRefreshCache(); @@ -1155,7 +1057,11 @@ namespace YLErp.Modules.RiskEngine foreach (var app in apps) { - ValidateRuleIdsActive(app.RuleIds); + 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) @@ -1166,7 +1072,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APPLICATION_BATCH_ENABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量启用应用配置"); + WriteAuditLog("APPLICATION_BATCH_ENABLE", "APPLICATION", app.id, app.ApplicationCode, app.ApplicationCode, "批量启用应用配置"); } DbContext.SaveChanges(); @@ -1197,7 +1103,7 @@ namespace YLErp.Modules.RiskEngine app.UpdateOptName = UserName; app.UpdateDate = DateTime.Now; - WriteAuditLog("APPLICATION_BATCH_DISABLE", "APPLICATION", app.id, ResolveRuleNames(app.RuleIds), "批量停用应用配置"); + WriteAuditLog("APPLICATION_BATCH_DISABLE", "APPLICATION", app.id, app.ApplicationCode, app.ApplicationCode, "批量停用应用配置"); } DbContext.SaveChanges(); @@ -1226,20 +1132,22 @@ namespace YLErp.Modules.RiskEngine if (!string.IsNullOrWhiteSpace(req.Keyword)) { - query = query.Where(v => v.VariableName.Contains(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.VariableName) + 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, - VariableExpr = v.VariableExpr, + IsImplemented = v.IsImplemented, Version = v.Version, SortOrder = v.SortOrder }); @@ -1251,22 +1159,22 @@ namespace YLErp.Modules.RiskEngine { var variable = GetVariableOrThrow(variableId); - var varExpr = variable.VariableExpr; var referenceCount = DbContext.glms_risk_rule - .Count(r => r.Status == RiskRuleStatus.Active && - ((r.ConditionJson != null && r.ConditionJson.Contains(variable.id.ToString())) || - (r.RuleExpr != null && varExpr != null && r.RuleExpr.Contains(varExpr)))); + .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, - VariableExpr = variable.VariableExpr, + ImplementationScript = variable.ImplementationScript, + SourceExpression = variable.SourceExpression, + IsImplemented = variable.IsImplemented, Version = variable.Version, SortOrder = variable.SortOrder, OptName = variable.OptName, @@ -1279,22 +1187,31 @@ namespace YLErp.Modules.RiskEngine 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.VariableExpr)) - throw new ServiceException("取值表达式不能为空"); - if (req.VariableExpr.Length > 10000) - throw new ServiceException("取值表达式长度不能超过 10000 字符"); + 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, - VariableExpr = req.VariableExpr, + ImplementationScript = req.ImplementationScript, + SourceExpression = req.SourceExpression, + IsImplemented = req.IsImplemented, SortOrder = req.SortOrder, Version = 1 }; @@ -1311,7 +1228,7 @@ namespace YLErp.Modules.RiskEngine DbContext.glms_risk_variable.Add(entity); DbContext.SaveChanges(); - WriteAuditLog("VARIABLE_CREATE", "VARIABLE", entity.id, req.VariableName, $"创建变量:{req.VariableName}", + WriteAuditLog("VARIABLE_CREATE", "VARIABLE", entity.id, req.VariableName, req.VariableCode, $"创建变量:{req.VariableCode}", snapshotData: JsonConvert.SerializeObject(new { Version = entity.Version })); DbContext.SaveChanges(); @@ -1339,16 +1256,13 @@ namespace YLErp.Modules.RiskEngine if (string.IsNullOrWhiteSpace(req.VariableName)) throw new ServiceException("变量名称不能为空"); - if (string.IsNullOrWhiteSpace(req.VariableExpr)) - throw new ServiceException("取值表达式不能为空"); + if (string.IsNullOrWhiteSpace(req.ImplementationScript)) + throw new ServiceException("实现脚本不能为空"); if (req.DataType != variable.DataType) { - var varExpr = variable.VariableExpr; var activeRefCount = DbContext.glms_risk_rule - .Count(r => r.Status == RiskRuleStatus.Active && - ((r.ConditionJson != null && r.ConditionJson.Contains(variable.id.ToString())) || - (r.RuleExpr != null && varExpr != null && r.RuleExpr.Contains(varExpr)))); + .Count(r => r.Status == RiskRuleStatus.Active && r.FormulaJson.Contains(variable.VariableCode)); if (activeRefCount > 0) throw new ServiceException($"该变量被 {activeRefCount} 个生效中的规则引用,无法修改数据类型"); } @@ -1359,14 +1273,16 @@ namespace YLErp.Modules.RiskEngine variable.Unit = req.Unit; variable.ValueDomain = req.ValueDomain; variable.Description = req.Description; - variable.VariableExpr = req.VariableExpr; + 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.VariableName}"); + WriteAuditLog("VARIABLE_UPDATE", "VARIABLE", variableId, req.VariableName, variable.VariableCode, $"修改变量:{variable.VariableCode}"); DbContext.SaveChanges(); InvalidateVariableCache(); @@ -1379,17 +1295,14 @@ namespace YLErp.Modules.RiskEngine { var variable = GetVariableOrThrow(variableId); - var varExpr = variable.VariableExpr; var activeRefCount = DbContext.glms_risk_rule - .Count(r => r.Status == RiskRuleStatus.Active && - ((r.ConditionJson != null && r.ConditionJson.Contains(variable.id.ToString())) || - (r.RuleExpr != null && varExpr != null && r.RuleExpr.Contains(varExpr)))); + .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.VariableName}"); + WriteAuditLog("VAR_DELETE", "VARIABLE", variableId, variable.VariableName, variable.VariableCode, $"删除变量:{variable.VariableCode}"); DbContext.SaveChanges(); InvalidateVariableCache(); @@ -1399,16 +1312,16 @@ namespace YLErp.Modules.RiskEngine public List GetAllVariableList() { var result = DbContext.glms_risk_variable - .Where(v => v.VariableExpr != null && v.VariableExpr != "") + .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, - VariableExpr = v.VariableExpr + Unit = v.Unit }) .ToList(); @@ -1446,6 +1359,7 @@ namespace YLErp.Modules.RiskEngine if (!string.IsNullOrWhiteSpace(req.Keyword)) { query = query.Where(l => l.TargetName.Contains(req.Keyword) + || l.TargetCode.Contains(req.Keyword) || l.OperationDetail.Contains(req.Keyword)); } @@ -1457,6 +1371,7 @@ namespace YLErp.Modules.RiskEngine TargetType = l.TargetType, TargetId = l.TargetId, TargetName = l.TargetName, + TargetCode = l.TargetCode, OperationDetail = l.OperationDetail, Result = l.Result, OptName = l.OptName, @@ -1484,6 +1399,7 @@ namespace YLErp.Modules.RiskEngine TargetType = log.TargetType, TargetId = log.TargetId, TargetName = log.TargetName, + TargetCode = log.TargetCode, OperationDetail = log.OperationDetail, Result = log.Result, SnapshotData = log.SnapshotData, diff --git a/YLErpWeb/App_Data/Config/otcformat.js b/YLErpWeb/App_Data/Config/otcformat.js index d4c79353..92b1f226 100644 --- a/YLErpWeb/App_Data/Config/otcformat.js +++ b/YLErpWeb/App_Data/Config/otcformat.js @@ -1,125 +1,2 @@ var main = main || {}; -main.formatOptions={ - "trading": { - "umprice": { - "precision": 9, - "grouping": true, - "rounded": true, - "percent": false, - "minDecimals": 9, - "maxDecimals": 0 - }, - "umpriceP": { - "percent": true, - "precision": 2, - "grouping": false, - "rounded": true, - "minDecimals": 2, - "maxDecimals": 0 - }, - "umpricePR": { - "precision": 4, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 4, - "maxDecimals": 2 - }, - "tradeSinglePrice": { - "precision": 2, - "grouping": true, - "rounded": true, - "percent": false, - "minDecimals": 2, - "maxDecimals": 0 - }, - "premiumRateP": { - "percent": true, - "precision": 2, - "grouping": false, - "rounded": true, - "minDecimals": 2, - "maxDecimals": 0 - }, - "premiumRate": { - "precision": 4, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 4, - "maxDecimals": 2 - }, - "tradePrice": { - "precision": 2, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 2, - "maxDecimals": 0 - }, - "StockEqvNotional": { - "precision": 2, - "grouping": true, - "rounded": true, - "percent": false, - "minDecimals": 2, - "maxDecimals": 0 - }, - "notional": { - "precision": 2, - "grouping": true, - "rounded": true, - "percent": false, - "minDecimals": 2, - "maxDecimals": 0 - }, - "notionalP": { - "percent": true, - "precision": 2, - "grouping": false, - "rounded": true, - "minDecimals": 2, - "maxDecimals": 0 - }, - "volatility": { - "precision": 4, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 4, - "maxDecimals": 2 - }, - "volatilityP": { - "percent": true, - "precision": 2, - "grouping": false, - "rounded": true, - "minDecimals": 2, - "maxDecimals": 0 - }, - "greek": { - "precision": 2, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 2, - "maxDecimals": 0 - }, - "marginRateP": { - "percent": true, - "precision": 2, - "grouping": false, - "rounded": true, - "minDecimals": 2, - "maxDecimals": 0 - }, - "marginRate": { - "precision": 4, - "grouping": false, - "rounded": true, - "percent": false, - "minDecimals": 4, - "maxDecimals": 2 - } - } -}; \ No newline at end of file +main.formatOptions = { "trading": { "umprice": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "umpriceP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "umpricePR": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "tradeSinglePrice": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "premiumRateP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "premiumRate": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "tradePrice": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "StockEqvNotional": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "notional": { "trimTailZeros": true, "precision": 9, "grouping": true, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "notionalP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "volatility": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "volatilityP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "greek": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 }, "marginRateP": { "trimTailZeros": true, "percent": true, "precision": 9, "grouping": false, "rounded": true, "minDecimals": 2, "maxDecimals": 9 }, "marginRate": { "trimTailZeros": true, "precision": 9, "grouping": false, "rounded": true, "percent": false, "minDecimals": 2, "maxDecimals": 9 } } }; \ No newline at end of file diff --git a/YLErpWeb/Common/BaseController.cs b/YLErpWeb/Common/BaseController.cs index be2e8e8a..e8224105 100644 --- a/YLErpWeb/Common/BaseController.cs +++ b/YLErpWeb/Common/BaseController.cs @@ -180,14 +180,6 @@ namespace YLErp.Web return; } var hasRight = false; - #if DEBUG - if (context.Controller is Controllers.RiskRuleController) - { - hasRight = true; - base.OnActionExecuting(context); - return; - } - #endif var isAjaxRequest = Request.Headers["X-Requested-With"] == "XMLHttpRequest"; var typedHeaders = Request.GetTypedHeaders(); From d5a4eab2ec1776bcfff9fc4516eb248c29298bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:14:04 +0800 Subject: [PATCH 14/17] =?UTF-8?q?Revert=20"feat(=E9=A3=8E=E6=8E=A7?= =?UTF-8?q?=E5=BC=95=E6=93=8E):=20RiskRuleService/RiskRuleController=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6=E5=8F=8A=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=AF=B9=E9=BD=90"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit a259b745ceb5d48114b424e83368b5d9823e6a65. --- .editorconfig | 14 - .../DBModels/Enums/RiskControlStrategy.cs | 9 - .../DBModels/Enums/RiskRuleStatus.cs | 9 - .../DBModels/Enums/RiskVariableCategory.cs | 10 - .../DBModels/Enums/RiskVariableDataType.cs | 9 - .../YLErp.Core/DBModels/glms_risk_rule.cs | 19 - .../DBModels/glms_risk_rule_application.cs | 23 - .../DBModels/glms_risk_rule_audit_log.cs | 20 - .../YLErp.Core/DBModels/glms_risk_variable.cs | 24 - .../DbUpdate/Ver-5.6.0/prod.sql | 92 -- YLErpDAL/DataBase/YLContext.cs | 7 +- .../RiskEngine/Dto/BatchOperationResult.cs | 10 - .../Dto/CreateRiskApplicationReq.cs | 16 - .../RiskEngine/Dto/CreateRiskRuleReq.cs | 10 - .../RiskEngine/Dto/CreateRiskVariableReq.cs | 19 - .../RiskEngine/Dto/FormulaCondition.cs | 15 - .../RiskEngine/Dto/FormulaDefinition.cs | 10 - .../RiskEngine/Dto/QueryRiskApplicationReq.cs | 13 - .../RiskEngine/Dto/QueryRiskAuditLogReq.cs | 14 - .../RiskEngine/Dto/QueryRiskRuleReq.cs | 12 - .../RiskEngine/Dto/QueryRiskVariableReq.cs | 11 - .../RiskEngine/Dto/RiskApplicationDetail.cs | 29 - .../RiskEngine/Dto/RiskApplicationListItem.cs | 28 - .../RiskEngine/Dto/RiskAuditLogDetail.cs | 20 - .../RiskEngine/Dto/RiskAuditLogListItem.cs | 18 - .../Dto/RiskRuleApplicationSummary.cs | 13 - .../Modules/RiskEngine/Dto/RiskRuleDetail.cs | 23 - .../RiskEngine/Dto/RiskRuleListItem.cs | 21 - .../RiskEngine/Dto/RiskRuleVersionItem.cs | 13 - .../RiskEngine/Dto/RiskVariableDetail.cs | 27 - .../RiskEngine/Dto/RiskVariableListItem.cs | 19 - .../RiskEngine/Dto/RiskVariableSimpleItem.cs | 14 - .../Dto/UpdateRiskApplicationReq.cs | 16 - .../RiskEngine/Dto/UpdateRiskRuleReq.cs | 11 - .../RiskEngine/Dto/UpdateRiskVariableReq.cs | 19 - .../Modules/RiskEngine/RiskEngineService.cs | 77 - .../Modules/RiskEngine/RiskRuleService.cs | 1414 ----------------- YLErpWeb/App_Data/FunctionRight.xml | 210 ++- YLErpWeb/Common/UserInfo.cs | 4 +- YLErpWeb/Controllers/RiskRuleController.cs | 616 ------- YLErpWeb/appsettings.dev.json | 25 +- 41 files changed, 110 insertions(+), 2873 deletions(-) delete mode 100644 Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs delete mode 100644 Framework/YLErp.Core/DBModels/Enums/RiskRuleStatus.cs delete mode 100644 Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs delete mode 100644 Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.cs delete mode 100644 Framework/YLErp.Core/DBModels/glms_risk_rule.cs delete mode 100644 Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs delete mode 100644 Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs delete mode 100644 Framework/YLErp.Core/DBModels/glms_risk_variable.cs delete mode 100644 Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/QueryRiskAuditLogReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskRuleVersionItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskRuleService.cs delete mode 100644 YLErpWeb/Controllers/RiskRuleController.cs diff --git a/.editorconfig b/.editorconfig index e2964ef3..40eb71f1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -160,17 +160,3 @@ csharp_preserve_single_line_statements = true # CA1819: Properties should not return arrays dotnet_diagnostic.CA1819.severity = silent - -# JavaScript 和 TypeScript 文件 -[*.{js,jsx,ts,tsx}] -# 缩进和间距 -indent_size = 4 -indent_style = space -tab_width = 4 - -# 新行首选项 -end_of_line = crlf -insert_final_newline = false - -# 拖尾逗号不添加 -trailing_comma = none diff --git a/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs b/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs deleted file mode 100644 index c8d8ce8a..00000000 --- a/Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YLErp.DBModels -{ - public enum RiskControlStrategy - { - Block = 1, - Approval = 2, - ShowTip = 3 - } -} diff --git a/Framework/YLErp.Core/DBModels/Enums/RiskRuleStatus.cs b/Framework/YLErp.Core/DBModels/Enums/RiskRuleStatus.cs deleted file mode 100644 index b4539183..00000000 --- a/Framework/YLErp.Core/DBModels/Enums/RiskRuleStatus.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YLErp.DBModels -{ - public enum RiskRuleStatus - { - Active = 1, - Disabled = 2, - Deleted = 3 - } -} diff --git a/Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs b/Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs deleted file mode 100644 index 28a9e859..00000000 --- a/Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace YLErp.DBModels -{ - public enum RiskVariableCategory - { - BookingElement = 1, - MarketData = 2, - SystemCalc = 3, - BooleanCheck = 4 - } -} diff --git a/Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.cs b/Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.cs deleted file mode 100644 index b5cd9b3d..00000000 --- a/Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace YLErp.DBModels -{ - public enum RiskVariableDataType - { - Numeric = 1, - Date = 2, - Boolean = 3 - } -} diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule.cs deleted file mode 100644 index cdb5896d..00000000 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace YLErp.DBModels -{ - [Table("glms_risk_rule")] - public class glms_risk_rule : DBModelWithOperator - { - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string RuleDescription { get; set; } - public string FormulaJson { get; set; } - public string FormulaText { get; set; } - public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active; - public int Version { get; set; } = 1; - public int? UpdateOptId { get; set; } - public string UpdateOptName { get; set; } - public DateTime? UpdateDate { get; set; } - } -} diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs deleted file mode 100644 index 0c731e27..00000000 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule_application.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace YLErp.DBModels -{ - [Table("glms_risk_rule_application")] - public class glms_risk_rule_application : DBModelWithOperator - { - public string ApplicationCode { get; set; } - public long RuleId { get; set; } - public RiskRuleStatus Status { get; set; } = RiskRuleStatus.Active; - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - public string ScopeAssetBookIds { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public bool ScopeIsGlobal { get; set; } - public int Version { get; set; } = 1; - public int? UpdateOptId { get; set; } - public string UpdateOptName { get; set; } - public DateTime? UpdateDate { get; set; } - } -} diff --git a/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs b/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs deleted file mode 100644 index 660927b0..00000000 --- a/Framework/YLErp.Core/DBModels/glms_risk_rule_audit_log.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace YLErp.DBModels -{ - [Table("glms_risk_rule_audit_log")] - public class glms_risk_rule_audit_log : DBModelBase - { - public string OperationType { get; set; } - public string TargetType { get; set; } - public long TargetId { get; set; } - public string TargetName { get; set; } - public string TargetCode { get; set; } - public string OperationDetail { get; set; } - public string Result { get; set; } - public string SnapshotData { get; set; } - public long OptId { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - } -} diff --git a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs b/Framework/YLErp.Core/DBModels/glms_risk_variable.cs deleted file mode 100644 index 8eff1191..00000000 --- a/Framework/YLErp.Core/DBModels/glms_risk_variable.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; - -namespace YLErp.DBModels -{ - [Table("glms_risk_variable")] - public class glms_risk_variable : DBModelWithOperator - { - public string VariableCode { get; set; } - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - public string ValueDomain { get; set; } - public string Description { get; set; } - public string ImplementationScript { get; set; } - public string SourceExpression { get; set; } - public bool IsImplemented { get; set; } - public int Version { get; set; } = 1; - public int SortOrder { get; set; } - public int? UpdateOptId { get; set; } - public string UpdateOptName { get; set; } - public DateTime? UpdateDate { get; set; } - } -} diff --git a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql b/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql deleted file mode 100644 index 8ba5327f..00000000 --- a/Framework/YLErp.Resources/DbUpdate/Ver-5.6.0/prod.sql +++ /dev/null @@ -1,92 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; - -CREATE TABLE `glms_risk_rule` ( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', - `RuleCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则编码(RISK-YYYYMMDD-NNNN)', - `RuleName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '规则名称', - `RuleDescription` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '规则描述', - `FormulaJson` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '公式JSON表达式', - `FormulaText` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '公式可读文本', - `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted', - `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', - `OptId` int NULL DEFAULT NULL COMMENT '创建人Id', - `OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', - `OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间', - `UpdateOptId` int NULL DEFAULT NULL COMMENT '最后修改人Id', - `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', - `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `uk_rule_code`(`RuleCode` ASC) USING BTREE, - INDEX `idx_status`(`Status` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则定义表' ROW_FORMAT = Dynamic; - -CREATE TABLE `glms_risk_rule_application` ( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', - `ApplicationCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '应用编码(APP-YYYYMMDD-NNNN)', - `RuleId` bigint NOT NULL COMMENT '关联规则Id', - `Status` tinyint NOT NULL DEFAULT 1 COMMENT '状态: 1=Active, 2=Disabled, 3=Deleted', - `ControlStrategy` tinyint NOT NULL COMMENT '控制策略: 1=Block, 2=Approval, 3=Warning', - `TriggerPoints` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '触发时点(逗号分隔)', - `ScopeAssetBookIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用账户Id列表', - `ScopeClientIds` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用对手方Id列表', - `ScopeUnderlyingTypes` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用标的类型', - `ScopeTradeTypes` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '适用合约类型', - `ScopeIsGlobal` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否全局适用: 0=否, 1=是', - `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', - `OptId` int NULL DEFAULT NULL COMMENT '创建人Id', - `OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', - `OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间', - `UpdateOptId` int NULL DEFAULT NULL COMMENT '最后修改人Id', - `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', - `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `uk_application_code`(`ApplicationCode` ASC) USING BTREE, - INDEX `idx_rule_id`(`RuleId` ASC) USING BTREE, - INDEX `idx_status`(`Status` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则应用配置表' ROW_FORMAT = Dynamic; - -CREATE TABLE `glms_risk_rule_audit_log` ( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', - `OperationType` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作类型(RULE_CREATE/RULE_UPDATE等)', - `TargetType` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '目标类型(RULE/APPLICATION/VARIABLE)', - `TargetId` bigint NOT NULL COMMENT '目标记录Id', - `TargetName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '目标名称', - `TargetCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '目标编码', - `OperationDetail` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '操作详情', - `Result` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作结果', - `SnapshotData` longtext CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '快照数据(JSON)', - `OptId` bigint NOT NULL COMMENT '操作人Id', - `OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作人名称', - `OptDate` datetime NOT NULL COMMENT '操作时间', - PRIMARY KEY (`id`) USING BTREE, - INDEX `idx_opt_date`(`OptDate` ASC) USING BTREE, - INDEX `idx_operation_type`(`OperationType` ASC) USING BTREE, - INDEX `idx_target_type`(`TargetType` ASC) USING BTREE, - INDEX `idx_target_id`(`TargetId` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控规则操作审计日志表' ROW_FORMAT = Dynamic; - -CREATE TABLE `glms_risk_variable` ( - `id` int NOT NULL AUTO_INCREMENT COMMENT '主键Id', - `VariableCode` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '变量编码', - `VariableName` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '变量名称', - `Category` tinyint NOT NULL COMMENT '分类: 1=BookingElement, 2=MarketData, 3=SystemCalc, 4=BooleanCheck', - `DataType` tinyint NOT NULL COMMENT '数据类型: 1=Numeric, 2=Date, 3=Boolean', - `Unit` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '单位', - `ValueDomain` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '值域描述', - `Description` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', - `ImplementationScript` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '实现脚本(C#表达式)', - `SourceExpression` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '来源表达式', - `IsImplemented` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已实现: 0=否, 1=是', - `Version` int NOT NULL DEFAULT 1 COMMENT '版本号(乐观锁)', - `SortOrder` int NOT NULL DEFAULT 0 COMMENT '排序', - `OptId` int NULL DEFAULT NULL COMMENT '创建人Id', - `OptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', - `OptDate` datetime NULL DEFAULT NULL COMMENT '创建时间', - `UpdateOptId` int NULL DEFAULT NULL COMMENT '最后修改人Id', - `UpdateOptName` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最后修改人名称', - `UpdateDate` datetime NULL DEFAULT NULL COMMENT '最后修改时间', - PRIMARY KEY (`id`) USING BTREE, - UNIQUE INDEX `uk_variable_code`(`VariableCode` ASC) USING BTREE -) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '风控变量池定义表' ROW_FORMAT = Dynamic; - -SET FOREIGN_KEY_CHECKS=1; diff --git a/YLErpDAL/DataBase/YLContext.cs b/YLErpDAL/DataBase/YLContext.cs index ce383a6d..b6ed6e6d 100644 --- a/YLErpDAL/DataBase/YLContext.cs +++ b/YLErpDAL/DataBase/YLContext.cs @@ -1,4 +1,4 @@ -using BaseOUDAL; +using BaseOUDAL; using YLErp.Core.DBModels; using YLErp.Model; @@ -408,10 +408,5 @@ namespace YLErp.BLL public DbSet tradeContractOaResult { get; set; } public DbSet bondPayment { get; set; } - public DbSet glms_risk_rule { get; set; } - public DbSet glms_risk_rule_application { get; set; } - public DbSet glms_risk_rule_audit_log { get; set; } - public DbSet glms_risk_variable { get; set; } - } } \ No newline at end of file diff --git a/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs b/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs deleted file mode 100644 index 0aed2a39..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/BatchOperationResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace YLErp.Modules.RiskEngine.Dto -{ - public class BatchOperationResult - { - public bool Success { get; set; } - public int TotalCount { get; set; } - public int SuccessCount { get; set; } - public string ErrorMessage { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs deleted file mode 100644 index f1b80da9..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskApplicationReq.cs +++ /dev/null @@ -1,16 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class CreateRiskApplicationReq - { - public long RuleId { get; set; } - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - public string ScopeAssetBookIds { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public bool ScopeIsGlobal { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs deleted file mode 100644 index ceceee6a..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskRuleReq.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace YLErp.Modules.RiskEngine.Dto -{ - public class CreateRiskRuleReq - { - public string RuleName { get; set; } - public string RuleDescription { get; set; } - public string FormulaJson { get; set; } - public string FormulaText { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs deleted file mode 100644 index 5299a549..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/CreateRiskVariableReq.cs +++ /dev/null @@ -1,19 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class CreateRiskVariableReq - { - public string VariableCode { get; set; } - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - public string ValueDomain { get; set; } - public string Description { get; set; } - public string ImplementationScript { get; set; } - public string SourceExpression { get; set; } - public bool IsImplemented { get; set; } - public int SortOrder { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs b/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs deleted file mode 100644 index af603b66..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/FormulaCondition.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace YLErp.Modules.RiskEngine.Dto -{ - public class FormulaCondition - { - public string VariableCode { get; set; } - public string VariableName { get; set; } - public string VariableType { get; set; } - public string Operator { get; set; } - public string ThresholdType { get; set; } - public object Value { get; set; } - public string ThresholdVariableCode { get; set; } - public string ThresholdVariableName { get; set; } - public string Unit { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs b/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs deleted file mode 100644 index cd85d0d7..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/FormulaDefinition.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class FormulaDefinition - { - public string LogicOperator { get; set; } - public List Conditions { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs deleted file mode 100644 index f195ee7c..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskApplicationReq.cs +++ /dev/null @@ -1,13 +0,0 @@ -using BaseOUDAL; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class QueryRiskApplicationReq : BaseSearchReq - { - public string Keyword { get; set; } - public RiskRuleStatus? Status { get; set; } - public RiskControlStrategy? Strategy { get; set; } - public string TriggerPoint { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskAuditLogReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskAuditLogReq.cs deleted file mode 100644 index 690712fa..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskAuditLogReq.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using BaseOUDAL; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class QueryRiskAuditLogReq : BaseSearchReq - { - public string OperationType { get; set; } - public string TargetType { get; set; } - public DateTime? StartDate { get; set; } - public DateTime? EndDate { get; set; } - public string Keyword { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs deleted file mode 100644 index 362316e4..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskRuleReq.cs +++ /dev/null @@ -1,12 +0,0 @@ -using BaseOUDAL; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class QueryRiskRuleReq : BaseSearchReq - { - public string Keyword { get; set; } - public RiskRuleStatus? Status { get; set; } - public string VariableCode { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs deleted file mode 100644 index cc9dc45a..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/QueryRiskVariableReq.cs +++ /dev/null @@ -1,11 +0,0 @@ -using BaseOUDAL; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class QueryRiskVariableReq : BaseSearchReq - { - public RiskVariableCategory? Category { get; set; } - public string Keyword { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs deleted file mode 100644 index a791dde8..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationDetail.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskApplicationDetail - { - public long Id { get; set; } - public string ApplicationCode { get; set; } - public long RuleId { get; set; } - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string FormulaJson { get; set; } - public string FormulaText { get; set; } - public RiskRuleStatus Status { get; set; } - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - public string ScopeAssetBookIds { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public bool ScopeIsGlobal { get; set; } - public int Version { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs deleted file mode 100644 index c04e7595..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskApplicationListItem.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskApplicationListItem - { - public long Id { get; set; } - public string ApplicationCode { get; set; } - public long RuleId { get; set; } - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string FormulaText { get; set; } - public RiskRuleStatus Status { get; set; } - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - public string ScopeAssetBookIds { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public bool ScopeIsGlobal { get; set; } - public int Version { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs deleted file mode 100644 index ea08ebe8..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogDetail.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskAuditLogDetail - { - public long Id { get; set; } - public string OperationType { get; set; } - public string TargetType { get; set; } - public long TargetId { get; set; } - public string TargetName { get; set; } - public string TargetCode { get; set; } - public string OperationDetail { get; set; } - public string Result { get; set; } - public string SnapshotData { get; set; } - public long OptId { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs deleted file mode 100644 index aabc739a..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskAuditLogListItem.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskAuditLogListItem - { - public long Id { get; set; } - public string OperationType { get; set; } - public string TargetType { get; set; } - public long TargetId { get; set; } - public string TargetName { get; set; } - public string TargetCode { get; set; } - public string OperationDetail { get; set; } - public string Result { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs deleted file mode 100644 index 3be19da8..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleApplicationSummary.cs +++ /dev/null @@ -1,13 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskRuleApplicationSummary - { - public long Id { get; set; } - public string ApplicationCode { get; set; } - public RiskRuleStatus Status { get; set; } - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs deleted file mode 100644 index 37251f3d..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleDetail.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskRuleDetail - { - public long Id { get; set; } - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string RuleDescription { get; set; } - public string FormulaJson { get; set; } - public string FormulaText { get; set; } - public RiskRuleStatus Status { get; set; } - public int Version { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - public List Applications { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs deleted file mode 100644 index 509bfe33..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleListItem.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskRuleListItem - { - public long Id { get; set; } - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string RuleDescription { get; set; } - public string FormulaText { get; set; } - public RiskRuleStatus Status { get; set; } - public int Version { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - public int ApplicationCount { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleVersionItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleVersionItem.cs deleted file mode 100644 index b0842d6e..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskRuleVersionItem.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskRuleVersionItem - { - public int Version { get; set; } - public string OperationType { get; set; } - public string OperationDetail { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs deleted file mode 100644 index 7470d12e..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableDetail.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskVariableDetail - { - public long Id { get; set; } - public string VariableCode { get; set; } - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - public string ValueDomain { get; set; } - public string Description { get; set; } - public string ImplementationScript { get; set; } - public string SourceExpression { get; set; } - public bool IsImplemented { get; set; } - public int Version { get; set; } - public int SortOrder { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - public int ReferenceCount { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs deleted file mode 100644 index 56ab0beb..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableListItem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskVariableListItem - { - public long Id { get; set; } - public string VariableCode { get; set; } - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - public string ValueDomain { get; set; } - public string Description { get; set; } - public bool IsImplemented { get; set; } - public int Version { get; set; } - public int SortOrder { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs b/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs deleted file mode 100644 index a414930f..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/RiskVariableSimpleItem.cs +++ /dev/null @@ -1,14 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class RiskVariableSimpleItem - { - public long Id { get; set; } - public string VariableCode { get; set; } - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs deleted file mode 100644 index 096f320e..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskApplicationReq.cs +++ /dev/null @@ -1,16 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class UpdateRiskApplicationReq - { - public RiskControlStrategy ControlStrategy { get; set; } - public string TriggerPoints { get; set; } - public string ScopeAssetBookIds { get; set; } - public string ScopeClientIds { get; set; } - public string ScopeUnderlyingTypes { get; set; } - public string ScopeTradeTypes { get; set; } - public bool ScopeIsGlobal { get; set; } - public int ExpectedVersion { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs deleted file mode 100644 index 1cbd7002..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskRuleReq.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace YLErp.Modules.RiskEngine.Dto -{ - public class UpdateRiskRuleReq - { - public string RuleName { get; set; } - public string RuleDescription { get; set; } - public string FormulaJson { get; set; } - public string FormulaText { get; set; } - public int ExpectedVersion { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs b/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs deleted file mode 100644 index f7db36bd..00000000 --- a/YLErpDAL/Modules/RiskEngine/Dto/UpdateRiskVariableReq.cs +++ /dev/null @@ -1,19 +0,0 @@ -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine.Dto -{ - public class UpdateRiskVariableReq - { - public string VariableName { get; set; } - public RiskVariableCategory Category { get; set; } - public RiskVariableDataType DataType { get; set; } - public string Unit { get; set; } - public string ValueDomain { get; set; } - public string Description { get; set; } - public string ImplementationScript { get; set; } - public string SourceExpression { get; set; } - public bool IsImplemented { get; set; } - public int SortOrder { get; set; } - public int ExpectedVersion { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 6c9311f8..433684ee 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -125,16 +125,6 @@ namespace YLErp.Modules.RiskEngine public class RiskEngineService : YLBaseService { IYcLogger _logger = LogFactory.GetLogger("RiskEngineService"); - - private static readonly Lazy _instance = - new Lazy(() => new RiskEngineService()); - - public static RiskEngineService GetInstance() => _instance.Value; - - private RiskEngineService() : base((OptUserInfo)null) - { - } - public RiskEngineService(OptUserInfo userInfo) : base(userInfo) { } @@ -147,73 +137,6 @@ namespace YLErp.Modules.RiskEngine { } - /// - /// 规则内存缓存(启动预热写入,EvaluateRisk 读取) - /// - private static volatile List _cachedRules; - private static volatile List _cachedApplications; - private static readonly object _cacheLock = new object(); - - /// - /// 预热:加载规则与应用到内存,并预编译所有规则到 RuleCompiledCache。 - /// 项目启动时调用一次;规则/应用更新后调用 RefreshCache 刷新。 - /// - public void Preload() - { - lock (_cacheLock) - { - _logger.Info("[风控引擎] Preload 开始 - 加载规则与应用并预编译"); - - var rules = LoadRulesFromDb(); - var applications = LoadApplicationsFromDb(); - List res = new List(); - foreach(var i in applications) - { - if(i.Status!= RiskRuleStatus.Active) - { - res.Add(i.Id); - } - } - // 预编译所有规则到 RuleCompiledCache - foreach (var rule in rules) - { - var ruleId = rule.Id.ToString(); - if (RuleCompiledCache.TryGet(ruleId, out _)) - { - continue; - } - - var compileResult = RuleCompiler.ValidateAndCompileRule(rule); - if (!compileResult.Success) - { - _logger.Info($"[风控引擎] 规则预编译失败 - RuleId: {rule.Id}, Error: {compileResult.ErrorMessage}"); - } - } - - _cachedRules = rules; - _cachedApplications = applications; - - _logger.Info($"[风控引擎] Preload 完成 - 规则数: {rules.Count}, 应用数: {applications.Count}"); - } - } - - /// - /// 刷新缓存:清空已编译委托与内存缓存后重新预加载。 - /// 规则/应用配置更新后调用。 - /// - public void RefreshCache() - { - _logger.Info("[风控引擎] RefreshCache 被调用 - 清空缓存并重新预加载"); - lock (_cacheLock) - { - _cachedRules = null; - _cachedApplications = null; - RuleCompiledCache.Clear(); - } - - Preload(); - } - /// /// 从内存缓存获取规则列表;缓存为空时兜底加载并填充缓存。 /// diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs deleted file mode 100644 index 58ebe35d..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleService.cs +++ /dev/null @@ -1,1414 +0,0 @@ -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详细设计.md(v1.1) - 需求来源文档:.trae/documents/风控引擎需求详细设计.md(v1.14) - - 核心概念: - - Rule(规则):定义"什么情况下触发",由变量 + 操作符 + 阈值组成的条件列表(FormulaJson) - - Application(应用配置):定义"何时、对谁、怎么处理",关联规则 + 策略/时点/范围(1个规则可配N个应用) - - Variable(变量):变量池中的可选项,用于构建规则条件(含实现脚本 ImplementationScript) - - AuditLog(审计日志):记录所有 CRUD 操作,支持版本追溯(SnapshotData 存储版本号快照) - -【架构位置】 - ┌─────────────────────────────────────────────────────────────────┐ - │ API 层 │ - │ RiskRuleController(27 个端点) │ - │ - /api/risk-rules/* 规则 CRUD(10 个端点) │ - │ - /api/risk-applications/* 应用配置 CRUD(9 个端点) │ - │ - /api/risk-variables/* 变量池 CRUD(6 个端点) │ - │ - /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) 批量删除规则(逐条保护检查,单事务) - GetRuleApplications(long) 获取规则关联的应用配置列表 - - ── 应用配置管理(9 个) ─────────────────────────────────────────── - QueryApplicationList(QueryRiskApplicationReq) 查询应用列表(Join 规则表,多条件筛选) - GetApplicationDetail(long) 获取应用详情(含关联规则信息) - CreateApplication(CreateRiskApplicationReq) 新建应用(关联规则 Active 校验,触发时点/Scope 校验) - UpdateApplication(long, UpdateRiskApplicationReq) 修改应用(乐观锁校验) - DeleteApplication(long) 删除应用(软删除) - EnableApplication(long) 启用应用(校验关联规则状态) - DisableApplication(long) 停用应用 - BatchEnableApplications(List) 批量启用(逐条校验关联规则) - BatchDisableApplications(List) 批量停用 - - ── 变量管理(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/ApplicationCode:Service 层自动生成,格式 RISK/APP-YYYYMMDD-NNNN - 6. FormulaJson 校验:变量存在性 + 操作符合法性(按 DataType 映射)+ 类型匹配 + 阈值变量引用 - 7. FormulaText:前端生成,后端仅校验非空并存储 - 8. Scope 字段:逗号分隔字符串存储,全局适用时不允许指定其他维度 - 9. 缓存刷新:写操作后调用 TryRefreshCache(),失败仅记日志不影响操作结果 - 10. 批量操作:单事务批量,全部成功或全部失败 - 11. 变量缓存:5 分钟本地 HashSet 缓存,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 - - 关键字段:SnapshotData(JSON 快照,存储版本号等) - -【关键文件清单】 - 本服务: - - RiskRuleService.cs(本文件):规则/应用/变量/日志 CRUD 管理 - - Dto/ 目录:请求/响应 DTO 模型 - - 关联服务: - - RiskEngineService.cs:引擎执行层(单例,本服务通过 GetInstance() 获取引用) - - RiskRuleController.cs(YLErpWeb/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.cs(Active/Disabled/Deleted) - - Framework/YLErp.Core/DBModels/Enums/RiskControlStrategy.cs(Block/Approve/Warn) - - Framework/YLErp.Core/DBModels/Enums/RiskVariableCategory.cs - - Framework/YLErp.Core/DBModels/Enums/RiskVariableDataType.cs(Numeric/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 _variableCodeCache; - private DateTime _variableCacheUpdateTime; - - private static readonly HashSet ValidTriggerPoints = new HashSet - { - "BOOK_CONFIRM", - "CLOSE_REVIEW", - "UPLOAD_CONFIRMATION", - "EVENT_TRIGGER", - "FUND_PAYMENT" - }; - - private static readonly Dictionary> ValidOperatorsByType = new Dictionary> - { - { RiskVariableDataType.Numeric, new HashSet { ">", "<", ">=", "<=", "=", "≠", "介于", "不介于" } }, - { RiskVariableDataType.Date, new HashSet { "<", ">", "=", ">=", "<=", "介于", "不介于" } }, - { RiskVariableDataType.Boolean, new HashSet { "是", "否" } } - }; - - 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 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 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(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 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 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(); - 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 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 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 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 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 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 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 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 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 - } -} diff --git a/YLErpWeb/App_Data/FunctionRight.xml b/YLErpWeb/App_Data/FunctionRight.xml index 1e03e280..2f01d359 100644 --- a/YLErpWeb/App_Data/FunctionRight.xml +++ b/YLErpWeb/App_Data/FunctionRight.xml @@ -1,29 +1,28 @@ - + - - - - - - - - + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -31,13 +30,13 @@ - - - - - - - + + + + + + + @@ -53,31 +52,14 @@ - + - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -89,39 +71,39 @@ - + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + @@ -129,62 +111,62 @@ - - - - + + + + - - + + - - - - - - + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - + - - - - - - - + + + + + + + @@ -212,28 +194,28 @@ - + - + - - - - - - - + + + + + + + - - + + - + diff --git a/YLErpWeb/Common/UserInfo.cs b/YLErpWeb/Common/UserInfo.cs index e5071920..d9c507eb 100644 --- a/YLErpWeb/Common/UserInfo.cs +++ b/YLErpWeb/Common/UserInfo.cs @@ -1,4 +1,4 @@ -using YLErp.DBModels.Consts; +using YLErp.DBModels.Consts; using YLErp.Model.Enum; using YLErp.Modules.ClientModule; using YLErp.Modules.ClientModule.Models; @@ -280,7 +280,7 @@ namespace YLErp.Web if (user.Roles.Any(n => n.Name == "权限管理员")) { - var rightStrs = new string[] { "部门管理", "系统管理", "角色管理", "角色新增", "角色修改", "用户管理", "用户新增", "用户修改", "用户禁用", "基础缓存", "风控规则查看", "风控规则新增", "风控规则编辑", "风控规则删除", "风控规则启停", "风控应用查看", "风控应用新增", "风控应用编辑", "风控应用删除", "风控应用启停", "风控变量查看", "风控变量新增", "风控变量编辑", "风控变量删除", "风控日志查看", "风控日志导出" }; + var rightStrs = new string[] { "部门管理", "系统管理", "角色管理", "角色新增", "角色修改", "用户管理", "用户新增", "用户修改", "用户禁用", "基础缓存" }; var query1 = db.Functions.Where(o => rightStrs.Contains(o.Name)).Select(n => n.Id); var query2 = from roleFunc in db.RoleFunctions join roleUsr in db.RoleUsers on roleFunc.RoleId equals roleUsr.RoleId diff --git a/YLErpWeb/Controllers/RiskRuleController.cs b/YLErpWeb/Controllers/RiskRuleController.cs deleted file mode 100644 index d65997dc..00000000 --- a/YLErpWeb/Controllers/RiskRuleController.cs +++ /dev/null @@ -1,616 +0,0 @@ -using Qdp.Foundation.Utilities; -using YLErp.Modules.RiskEngine; -using YLErp.Modules.RiskEngine.Dto; - -namespace YLErp.Web.Controllers -{ - [Route("risk-engine")] - public class RiskRuleController : BaseController - { - private readonly IYcLogger _logger = LogFactory.GetLogger("RiskRuleController"); - - #region Rule Management - - [HttpGet("risk-rules")] - [MyAuthorize("风控规则查看")] - public JsonResult QueryRiskRules(QueryRiskRuleReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.QueryRuleList(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-rules/{id}")] - [MyAuthorize("风控规则查看")] - public JsonResult GetRiskRule(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetRuleDetail(id); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-rules/{id}/versions")] - [MyAuthorize("风控规则查看")] - public JsonResult GetRiskRuleVersions(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetRuleVersions(id); - 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")] - [MyAuthorize("风控规则新增")] - public JsonResult CreateRiskRule([FromBody] CreateRiskRuleReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.CreateRule(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpPut("risk-rules/{id}")] - [MyAuthorize("风控规则编辑")] - public JsonResult UpdateRiskRule(long id, [FromBody] UpdateRiskRuleReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.UpdateRule(id, req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpDelete("risk-rules/{id}")] - [MyAuthorize("风控规则删除")] - public JsonResult DeleteRiskRule(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.DeleteRule(id); - return Json(new { success = true }); - } - catch (ServiceException ex) - { - return Json(new { success = false, message = ex.Message }); - } - catch (Exception ex) - { - _logger.Error(ex, "删除风控规则"); - return Json(new { success = false, message = "系统异常,请联系管理员" }); - } - } - - [HttpDelete("risk-rules/batch")] - [MyAuthorize("风控规则删除")] - public JsonResult BatchDeleteRiskRules([FromBody] List ids) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.BatchDeleteRules(ids); - 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/{id}/enable")] - [MyAuthorize("风控规则启停")] - public JsonResult EnableRiskRule(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.EnableRule(id); - return Json(new { success = true }); - } - 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/{id}/disable")] - [MyAuthorize("风控规则启停")] - public JsonResult DisableRiskRule(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.DisableRule(id); - return Json(new { success = true }); - } - 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("风控规则查看")] - public JsonResult GetRuleApplications(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetRuleApplications(id); - 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 Application Management - - [HttpGet("risk-applications")] - [MyAuthorize("风控应用查看")] - public JsonResult QueryRiskApplications(QueryRiskApplicationReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.QueryApplicationList(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-applications/{id}")] - [MyAuthorize("风控应用查看")] - public JsonResult GetRiskApplication(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetApplicationDetail(id); - 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-applications")] - [MyAuthorize("风控应用新增")] - public JsonResult CreateRiskApplication([FromBody] CreateRiskApplicationReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.CreateApplication(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpPut("risk-applications/{id}")] - [MyAuthorize("风控应用编辑")] - public JsonResult UpdateRiskApplication(long id, [FromBody] UpdateRiskApplicationReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.UpdateApplication(id, req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpDelete("risk-applications/{id}")] - [MyAuthorize("风控应用删除")] - public JsonResult DeleteRiskApplication(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.DeleteApplication(id); - return Json(new { success = true }); - } - 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-applications/{id}/enable")] - [MyAuthorize("风控应用启停")] - public JsonResult EnableRiskApplication(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.EnableApplication(id); - return Json(new { success = true }); - } - 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-applications/{id}/disable")] - [MyAuthorize("风控应用启停")] - public JsonResult DisableRiskApplication(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.DisableApplication(id); - return Json(new { success = true }); - } - 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-applications/batch-enable")] - [MyAuthorize("风控应用启停")] - public JsonResult BatchEnableRiskApplications([FromBody] List ids) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.BatchEnableApplications(ids); - 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-applications/batch-disable")] - [MyAuthorize("风控应用启停")] - public JsonResult BatchDisableRiskApplications([FromBody] List ids) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.BatchDisableApplications(ids); - 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 - - [HttpGet("risk-variables")] - [MyAuthorize("风控变量查看")] - public JsonResult QueryRiskVariables(QueryRiskVariableReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.QueryVariableList(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-variables/{id}")] - [MyAuthorize("风控变量查看")] - public JsonResult GetRiskVariable(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetVariableDetail(id); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-variables/list")] - [MyAuthorize("风控变量查看")] - public JsonResult GetAllRiskVariables() - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetAllVariableList(); - 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-variables")] - [MyAuthorize("风控变量新增")] - public JsonResult CreateRiskVariable([FromBody] CreateRiskVariableReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.CreateVariable(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpPut("risk-variables/{id}")] - [MyAuthorize("风控变量编辑")] - public JsonResult UpdateRiskVariable(long id, [FromBody] UpdateRiskVariableReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.UpdateVariable(id, req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpDelete("risk-variables/{id}")] - [MyAuthorize("风控变量删除")] - public JsonResult DeleteRiskVariable(long id) - { - try - { - var service = new RiskRuleService(CurUser); - service.DeleteVariable(id); - return Json(new { success = true }); - } - 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 Audit Log - - [HttpGet("risk-audit-logs")] - [MyAuthorize("风控日志查看")] - public JsonResult QueryRiskAuditLogs(QueryRiskAuditLogReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.QueryAuditLogs(req); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-audit-logs/{id}")] - [MyAuthorize("风控日志查看")] - public JsonResult GetRiskAuditLogDetail(long id) - { - try - { - var service = new RiskRuleService(CurUser); - var result = service.GetAuditLogDetail(id); - 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 = "系统异常,请联系管理员" }); - } - } - - [HttpGet("risk-audit-logs/export")] - [MyAuthorize("风控日志导出")] - public IActionResult ExportRiskAuditLogs(QueryRiskAuditLogReq req) - { - try - { - var service = new RiskRuleService(CurUser); - var bytes = service.ExportAuditLogs(req); - return File(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "风控操作日志.xlsx"); - } - catch (ServiceException ex) - { - return Json(new { success = false, message = ex.Message }); - } - catch (Exception ex) - { - _logger.Error(ex, "导出风控操作日志"); - return Json(new { success = false, message = "系统异常,请联系管理员" }); - } - } - - #endregion - } -} diff --git a/YLErpWeb/appsettings.dev.json b/YLErpWeb/appsettings.dev.json index f53778f2..378136e1 100644 --- a/YLErpWeb/appsettings.dev.json +++ b/YLErpWeb/appsettings.dev.json @@ -7,15 +7,15 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "ylcms": "server=10.250.202.35;uid=onederiv@OneDeriv#wx_dtpp_test;pooling=true;port=2883;pwd=Onederiv@2026;database=yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "yladmin": "server=10.250.202.35;uid=onederiv@OneDeriv#wx_dtpp_test;pooling=true;port=2883;pwd=Onederiv@2026;database=yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "ylclient": "server=10.250.202.35;uid=onederiv@OneDeriv#wx_dtpp_test;pooling=true;port=2883;pwd=Onederiv@2026;database=yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", - "bondoms": "server=10.250.202.35;uid=onederiv@OneDeriv#wx_dtpp_test;pooling=true;port=2883;pwd=Onederiv@2026;database=bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;" + "ylcms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_ylcms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "yladmin": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_admin;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "ylclient": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_yltrs_client;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;", + "bondoms": "server=192.168.2.96;uid=DBAdmin;pooling=true;port=3306;pwd=YieldChain$$2025;database=glms_bond_oms;charset=utf8;Allow User Variables=True;SslMode=none;Connection Timeout=30;IgnoreCommandTransaction=true;" }, "AppSettings": { "VirtualPathRoot": "", "UseRightAligned": "", - "PluginFolder": "D:\\workspace\\onederiv\\trs\\Plugins\\YLErp.Plugins.GuoLian\\obj\\Debug\\net6.0" // 改为本地目录地址 + "PluginFolder": "D:\\workspace\\glms\\zszq-trs\\Plugins\\YLErp.Plugins.GuoLian\\obj\\Debug\\net6.0" }, "LibreOffice": { "ExePath": "", @@ -75,15 +75,12 @@ "SendKafakaCron": "10 0 * * *", "MaxThreadPercent": 0.6, //占系统资源最多60%的线程数量 "oa_confg": { - "BaseUrl": "http://10.250.202.40:13590", - "SubUrl": "/gateway/oaflow/createOaFlow", - "QueryUrl": "/gateway/oaflow/getRequestData", - "DownloadFileUrl": "/gateway/oaflow/downloadSingleFile", - "SystemToken": "QIkox8sv3rVLtiT1wmI4Dp8wSjX9D84Glkp121vRino9fLobWnDiy8ppmqJcv1676TYuxZsGrYelx0whRCMaPyums2VnzIYrpqCFf5tyc3/x22Elf25xgRmd5pH8tLcnPuVOjMrUf0GsoytQCGFkhnrPajhsKq/mSwYQ4OA=", //OA系统key - "SystemName": "onederiv", - "IsNextFlow": 1, //是否自动提交 默认0 - "FlowId": 779115, - "UploadUrl": "http://10.250.202.40:13590/gateway/oaflow/uploadDoc" + "BaseUrl": "http://ip:port", + "SubUrl": "", + "UploadUrl": "/api/oa/upload", // OA附件上传接口路径 + "SystemToken": "your-system-token", // OA系统鉴权Token + "SystemName": "OTC_TRADE", // 调用方系统标识 + "IsNextFlow": 0 }, "GeneralSSO": { "enable": true, //是否启用通用SSO登录 From 8cff296be3cc6787613f7fd0b7cfeb3a718ab5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:14:24 +0800 Subject: [PATCH 15/17] =?UTF-8?q?Revert=20"feat:=20=E9=A3=8E=E6=8E=A7?= =?UTF-8?q?=E5=BC=95=E6=93=8E=E6=A0=B8=E5=BF=83=E9=87=8D=E6=9E=84=20-=20?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E6=A0=91=E7=BC=96=E8=AF=91=20+=20Ro?= =?UTF-8?q?slyn=20Scripting=20+=20=E8=A7=84=E5=88=99=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=90=88=E5=B9=B6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3e357312c2ffd63c162c9ce208795e1e03112273. --- .../RiskEngine/Compile/RuleCompiler.cs | 297 --------- .../Modules/RiskEngine/RiskEngineService.cs | 605 ++++-------------- YLErpDAL/Modules/RiskEngine/RiskRule.cs | 51 -- YLErpDAL/YLErpDAL.csproj | 4 +- 4 files changed, 134 insertions(+), 823 deletions(-) delete mode 100644 YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskRule.cs diff --git a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs deleted file mode 100644 index 55115803..00000000 --- a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiler.cs +++ /dev/null @@ -1,297 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.Scripting; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// Roslyn 脚本全局变量容器 - /// CSharpScript 执行时通过此对象传递 DataMap - /// - public class ScriptGlobals - { - /// - /// 数据字典:key=表名/前缀,value=对应数据对象 - /// - public Dictionary DataMap { get; set; } - } - - /// - /// 规则编译器(Roslyn 版) - /// - /// 编译时机:规则定义时(零运行时解析开销) - /// 执行方式:判风控时直接调用 rule.CompiledScript(context) → bool - /// - /// 相比表达式树方案的优势: - /// - 代码简洁:生成 C# 字符串即可,无需手动拼接 Expression 节点 - /// - 灵活性高:天然支持多条件 AND/OR、复杂运算、未来可扩展任意 C# 语法 - /// - 可调试:生成的脚本代码可直接阅读和理解 - /// - /// 示例: - /// FormulaJson: { "conditions": [{"variableCode":"trade.StockEqvNotional","operator":">","value":100000000}] } - /// 生成脚本:Convert.ToDecimal(((YLErp.DBModels.trade)DataMap["trade"]).StockEqvNotional) > 100000000m - /// 编译结果:Func<RiskContext, bool>(内部通过 Roslyn 编译缓存) - /// - public static class RuleCompiler - { - /// - /// 变量编码 → 类型全名的映射 - /// 用于 Roslyn 脚本中的类型强转,如 ((YLErp.DBModels.trade)DataMap["trade"]) - /// - static IYcLogger _logger =LogFactory.GetLogger("RuleCompiler"); - private static readonly Dictionary VariableTypeMap = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - ["trade"] = "YLErp.DBModels.trade", - ["swap_position"] = "YLErp.DBModels.swap_position", - ["client"] = "YLErp.DBModels.client", - ["credit"] = "YLErp.DBModels.credit", - ["client_marginrate"] = "YLErp.DBModels.client_marginrate", - ["market"] = "YLErp.DBModels.market", - ["calc"] = "YLErp.DBModels.calc", - ["sys"] = "YLErp.DBModels.sys", - ["eod_swap_position"] = "YLErp.DBModels.eod_swap_position", - ["realtime_trade_risk"] = "YLErp.DBModels.realtime_trade_risk", - }; - - /// - /// 将规则编译为可执行委托,并写入 rule.CompiledScript - /// 调用时机:规则创建时 / 规则加载时 / 缓存刷新时 - /// - public static void Compile(this RiskRule rule) - { - if (string.IsNullOrWhiteSpace(rule.FormulaJson)) - { - throw new ArgumentException("规则 FormulaJson 不能为空", nameof(rule.FormulaJson)); - } - - // 1. 解析 FormulaJson - var conditions = ParseFormulaJson(rule.FormulaJson); - - // 2. 生成 Roslyn C# 脚本代码 - string scriptCode = BuildScriptCode(conditions); - - // 3. 编译脚本(只编译一次,后续直接执行) - var compiled = CompileScript(scriptCode); - - // 4. 写入规则(不持久化,仅内存缓存) - rule.CompiledScript = compiled; - } - - /// - /// 解析 FormulaJson,提取条件列表 - /// - private static List<(string variableCode, string op, object value, string variableType)> ParseFormulaJson(string formulaJson) - { - var result = new List<(string, string, object, string)>(); - var jObj = JObject.Parse(formulaJson); - var conditions = jObj["conditions"] as JArray; - - if (conditions == null || !conditions.Any()) - { - throw new ArgumentException("FormulaJson 中缺少 conditions"); - } - - try - { - string variableCode = cond["variableCode"]?.Value(); - string op = cond["operator"]?.Value(); - object value = cond["value"]?.Value(); - string variableType = cond["variableType"]?.Value() ?? "numeric"; - - if (string.IsNullOrWhiteSpace(variableCode) || string.IsNullOrWhiteSpace(op)) - { - throw new ArgumentException("条件中缺少 variableCode 或 operator"); - } - - result.Add((variableCode, op, value, variableType)); - } - - var result = ValidateAndCompileFormula(rule.RuleExpr); - if (!result.Success) - { - return RuleCompileResult.Fail($"规则[{rule.Id}]编译失败:{result.ErrorMessage}"); - } - - rule.CompiledScript = result.CompiledScript; - RuleCompiledCache.Set(rule.Id.ToString(), result.CompiledScript); - return result; - } - - /// - /// 根据条件列表生成 Roslyn C# 脚本代码 - /// 多条件自动用 &&(AND)连接 - /// - private static string BuildScriptCode(List<(string variableCode, string op, object value, string variableType)> conditions) - { - var exprParts = new List(); - - foreach (var (variableCode, op, value, variableType) in conditions) - { - // 解析 variableCode:前缀.属性名 - var parts = variableCode.Split('.'); - if (parts.Length != 2) - { - throw new ArgumentException($"variableCode 格式不正确:{variableCode}"); - } - - string prefix = parts[0].Trim().ToLower(); - string propertyName = parts[1].Trim(); - - if (!VariableTypeMap.TryGetValue(prefix, out string typeFullName)) - { - throw new ArgumentException($"未知前缀:{prefix}"); - } - - // 格式化阈值 - string valueLiteral = FormatValueLiteral(value, variableType); - - // 格式化操作符 - string csharpOp = FormatOperator(op); - - // 生成单条件表达式 - string expr = $"{leftExpr} {csharpOp} {valueLiteral}"; - exprParts.Add(expr); - } - - // 多条件用 && 连接 - return string.Join(" && ", exprParts); - } - - /// - /// 将前端传入的 expression 转成 Roslyn 可执行的成员访问表达式。 - /// 例如 trade.StockEqvNotional 会转成 ((YLErp.DBModels.trade)DataMap["trade"]).StockEqvNotional。 - /// 数值类型会自动包一层 Convert.ToDecimal,便于与 decimal 阈值比较。 - /// - private static string BuildMemberAccessExpression(string expression, string variableType) - { - var parts = expression.Split('.'); - if (parts.Length < 2) - { - throw new ArgumentException($"expression 格式不正确:{expression}"); - } - - string prefix = parts[0].Trim().ToLower(); - if (!VariableTypeMap.TryGetValue(prefix, out string typeFullName)) - { - throw new ArgumentException($"未知前缀:{prefix}"); - } - - // 除前缀外,其余部分都视为成员访问路径,便于后续扩展多级属性访问 - string memberAccess = string.Join(".", parts.Skip(1).Select(p => p.Trim())); - string objectExpr = $"(({typeFullName})DataMap[\"{prefix}\"]).{memberAccess}"; - - if (variableType == "numeric" || variableType == "number") - { - return $"Convert.ToDecimal({objectExpr})"; - } - - return objectExpr; - } - - /// - /// 用 Roslyn 编译 C# 脚本代码为可执行委托 - /// - private static Func CompileScript(string scriptCode) - { - // 配置编译选项:引用必要的程序集 - var options = ScriptOptions.Default - .WithReferences( - typeof(RiskContext).Assembly, // YLErpDAL - typeof(YLErp.DBModels.trade).Assembly // Model 所在程序集 - ) - .WithImports("System"); - - // 创建脚本(尚未执行,仅编译) - var script = CSharpScript.Create(scriptCode, options, globalsType: typeof(ScriptGlobals)); - - // 编译(提前发现语法错误) - //后续编译需提供接口,返回前端编译信息,包含编译错误列表 - var compilation = script.GetCompilation(); - var diagnostics = compilation.GetDiagnostics(); - var errors = diagnostics.Where(d => d.Severity == Microsoft.CodeAnalysis.DiagnosticSeverity.Error).ToList(); - if (errors.Any()) - { - string errorMsg = string.Join("; ", errors.Select(e => e.GetMessage())); - _logger.Error($"规则编译失败:{errorMsg}\n脚本代码:{scriptCode}"); - throw new InvalidOperationException($"脚本编译失败:{errorMsg}"); - } - - // 生成可调用委托 - var runner = script.CreateDelegate(); - - // 包装为同步的 Func - return ctx => - { - var globals = new ScriptGlobals { DataMap = ctx.DataMap }; - try - { - return runner(globals).GetAwaiter().GetResult(); - } - catch (Exception ex) - { - // 脚本执行异常(如空引用、类型转换失败)视为规则不触发 - _logger.Error($"规则执行异常:{ex.Message}\n脚本代码:{scriptCode}"); - return false; - } - }; - } - - /// - /// 格式化阈值为 C# 字面量 - /// - private static string FormatValueLiteral(object value, string variableType) - { - if (value == null) return "null"; - - string strValue = value.ToString(); - - // 数值类型统一加 m 后缀(decimal) - if (variableType == "numeric" || variableType == "number") - { - if (decimal.TryParse(strValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal d)) - { - return d.ToString(CultureInfo.InvariantCulture) + "m"; - } - } - - // 布尔 - if (variableType == "boolean" || variableType == "bool") - { - if (bool.TryParse(strValue, out bool b)) - { - return b ? "true" : "false"; - } - } - - // 日期 - if (variableType == "date" && DateTime.TryParse(strValue, out DateTime dt)) - { - return $"DateTime.Parse(\"{dt:yyyy-MM-dd}\")"; - } - - // 字符串(兜底) - return $"\"{strValue.Replace("\\", "\\\\").Replace("\"", "\\\"")}\""; - } - - /// - /// 将操作符统一为 C# 操作符 - /// - private static string FormatOperator(string op) - { - return op?.Trim() switch - { - "=" or "==" => "==", - "!=" or "≠" => "!=", - ">" => ">", - "<" => "<", - ">=" => ">=", - "<=" => "<=", - _ => throw new ArgumentException($"不支持的操作符:{op}") - }; - } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs index 433684ee..c165f109 100644 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs @@ -1,6 +1,4 @@ -using Newtonsoft.Json; using Qdp.Foundation.Utilities; -using System.Reflection; using YLErp.BLL; using YLErp.Model; @@ -14,45 +12,50 @@ using YLErp.Model; 时点执行可配置规则判断。当前目标不是一次性做成最终版,而是在最小可运行链路 跑通后,逐步演进为可由前端配置、后端预编译、执行期直接命中的正式版本。 -【当前实现口径】(以本注释和实际代码为准) + 本模块旨在将现有限额监控升级为完整的风控引擎,实现规则的灵活配置、公式化执行、 + 策略判定与结果输出。 + +【设计文档】 + 详细设计文档路径: + X:\尹峰\onederiv\风控引擎需求详细设计.md 核心概念: - - RiskRule(风控规则):单条规则包含完整的公式定义 + 校验策略 + 触发时点 + 适用范围。 - 不需要 "一个 Rule 对应多个 Application" 的复杂关系。 - - Content:用户输入的规则内容字符串,如"名义本金>1亿" - - 规则编译:在规则定义时(创建/加载/缓存刷新时)调用 RuleCompiler.Compile(rule), - 将 Content 解析并编译为 Func 委托,写入 rule.CompiledScript。 - 判风控时直接执行委托,零解析开销。 + - Rule(规则):定义"什么情况下触发",由变量 + 操作符 + 阈值组成的条件列表 + - Application(应用):定义"何时、对谁、怎么处理",关联规则 + 策略/时点/范围 + - Variable(变量):变量池中的可选项,用于构建规则条件 - RiskContext:一次风控检查的数据上下文(DataMap 机制) - - RiskResult:风控检查结果(Passed / Blocked / NeedApproval / Warnings / TriggeredRules) + - RiskResult:风控检查结果(Passed / Blocked / NeedApproval / Warnings) - 当前编译流程: - 1. 外部准备好规则对象,在 RuleExpr 中直接写最终可执行表达式 - 2. 调用 RuleCompiler.ValidateAndCompileRule(rule) - 3. 内部使用 Roslyn 编译 RuleExpr,生成 Func - 4. 编译成功后写入 rule.CompiledScript,并同步写入 RuleCompiledCache - 5. 执行阶段优先从 RuleCompiledCache 取委托执行 - - 当前执行流程(EvaluateRisk): - 1. 从数据库加载规则列表和应用列表 - 2. 按应用状态、TriggerPoints 过滤有效应用 - 3. 按应用范围过滤交易是否命中(全局 / 账户 / 客户 / 标的类型 / 合约类型) - 4. 根据应用配置中的 RuleIds 找到对应规则 Id 并关联规则 - 5. 优先从 RuleCompiledCache 读取已编译委托,未命中时兜底编译一次 - 6. 执行规则委托,按 ControlStrategy 聚合为 Blocked / NeedApproval / Warnings - 7. 返回 RiskResult - - 与旧设计文档的差异: - - 删除 RiskRuleApplication 模型,将策略/时点/范围合并到 RiskRule 中 - - 删除 FormulaJson 实时解析,改为 Content → 表达式树编译 - - 规则编译跟随规则生命周期(创建/加载时),判风控时零解析 - -【与早期方案的主要差异】 - - 当前不是 Content 解析或表达式树主导,而是 RuleExpr 直编译主导 - - RiskRule 与 RiskRuleApplication 当前仍是分离模型,没有合并 - - 编译器文件已放入 RiskEngine/Compile 目录下 - - 当前已引入 RuleCompileResult、RuleCompiledCache,用于校验结果与进程内缓存 - - Rule2 这类“左值与右值都来自对象字段”的规则,当前通过 RuleExpr 直接表达 +【架构设计】 + ┌─────────────────────────────────────────────────────────────────┐ + │ API 层(RiskRuleController / RiskEngineController) │ + │ - 规则 CRUD API、应用配置 CRUD API、变量池 CRUD API │ + │ - 风控执行 API(EvaluateRisk / Trial) │ + └──────────────────────────┬──────────────────────────────────────┘ + │ + ┌──────────────────────────▼──────────────────────────────────────┐ + │ 风控引擎层(YLErpDAL/Modules/RiskEngine/) ← 本文件夹所在层 │ + │ │ + │ RiskEngineService(本文件) │ + │ - 内存缓存管理(规则 + 应用 + 变量 + 编译后脚本) │ + │ - 构造 RiskContext → 解析 FormulaJson → 执行条件判断 → 输出结果 │ + │ │ + │ RiskVariableProvider(待实现) │ + │ - 接收已编译委托 + RiskContext → 执行计算 → 返回变量值 │ + │ │ + │ RiskRuleService(待实现) │ + │ - 规则 / 应用 / 变量 / 日志的 CRUD │ + │ - CRUD 后触发 RiskEngineService.RefreshCache() │ + │ │ + │ RiskAuditLogService(待实现) │ + │ - 操作日志写入 / 查询 │ + └──────────────────────────┬──────────────────────────────────────┘ + │ + ┌──────────────────────────▼──────────────────────────────────────┐ + │ 数据访问层 │ + │ - RiskRuleRepository(规则 + 应用 + 变量 + 日志) │ + │ - 复用现有 DAL(Trade / Client / SwapPosition / Credit 等) │ + └─────────────────────────────────────────────────────────────────┘ 【当前代码结构】 风控引擎层:YLErpDAL/Modules/RiskEngine/ @@ -72,48 +75,92 @@ using YLErp.Model; 【当前进度】(截至 2026-06-24) ✅ 已完成: 1. 创建 RiskEngine 文件夹,与 RiskModule 平级,实现代码隔离 - 2. 核心模型: - - RiskRule.cs:合并模型(公式 + 策略 + 时点 + 范围 + CompiledScript) + 2. 创建核心模型: - RiskContext.cs:风控上下文(TradeId, TriggerPoint, DataMap) - RiskResult.cs:风控结果(Passed, Blocked, NeedApproval, Warnings, TriggeredRules) - - RiskFormula.cs:FormulaJson 反序列化模型(辅助) - 3. RuleCompiler.cs: - - ParseContent:解析 Content 字符串(支持 > < >= <= = != ≠) - - ParseValue:解析数值(支持 万/亿/千/百分比) - - BuildExpression:表达式树构建(反射取值 + decimal 比较) - 4. RiskEngineService.EvaluateRisk: - - 加载示例规则列表 - - 筛选 TriggerPoint 匹配的规则 - - 调用 rule.CompiledScript(context) 执行预编译委托 - - 按 ControlStrategy 聚合结果(禁止/审批/提示) - 5. 集成到现有流程: - - QuotaMonitorService.QuotaCheck() 末尾已接入风控引擎 + 3. RiskEngineService 骨架: + - 继承 YLBaseService,保留三个构造函数 + - Evaluate():1==1 占位方法,验证服务可实例化 + - EvaluateRisk(RiskContext, triggerPoint):按设计文档预演的入口方法 + 4. 集成到现有流程: + - QuotaMonitorService.QuotaCheck() 末尾已插入风控引擎调用 - 触发时点:BOOK_CONFIRM(簿记交易确认) - 6. 编译验证通过 + - 构造 RiskContext,传入 trade 对象到 DataMap + - 按策略(Blocked/NeedApproval/Warnings)映射回 QuotaTrialStatusEnum + 5. 编译验证通过(YLErpDAL.csproj 0 error, 80 warnings 为旧代码) -【待办事项 / TODO】 - ⬜ 1. 接入真实规则来源 - - 由数据库或前端提交替换当前 LoadRules / LoadApplications 的规则来源 - ⬜ 2. 增加接口层 - - 提供前端提交 RuleExpr 后的校验接口 - - 提供规则保存 / 发布后预编译并写缓存的入口 - ⬜ 3. 启动预热与缓存刷新 - - 服务启动时批量加载有效规则并预编译 - - 支持规则更新后的缓存刷新 / 删除 - ⬜ 4. 完善执行期策略 - - 当前保留“缓存未命中时兜底编译”逻辑 - - 后续可收紧为“执行期只读缓存,未命中按发布失败处理” - ⬜ 5. 补测试样例 - - 数值比较、日期比较、维度过滤、非法公式、边界值 - ⬜ 6. 完善 RuleExpr 配套能力 - - 提供前端可用的表达式编辑、校验与错误提示 - - 约束可用变量、类型转换和脚本安全边界 +【待办事项 / TODO】(按优先级排序) + ⬜ 1. 内存缓存机制 + - 添加 _cachedRules / _cachedApplications / _cachedVariables / _compiledScripts + - 实现 RefreshCache() 方法,从数据库加载 Active 规则/应用/变量 + - CRUD 操作后即时刷新(由 RiskRuleService 调用) + ⬜ 2. 数据库表创建(SQL) + - glms_risk_rule(规则定义表) + - glms_risk_rule_application(规则应用表) + - glms_risk_variable(变量池定义表) + - glms_risk_rule_audit_log(操作日志表) + ⬜ 3. 规则筛选逻辑 + - 按 TriggerPoint 过滤应用配置 + - 按 Scope(全局/账户/对手方/标的类型/合约类型)过滤适用范围 + - 去重(同一规则可能被多个应用引用) + ⬜ 4. 公式解析与执行 + - 解析 FormulaJson(条件列表,AND 关系) + - 数值型 / 日期型 / 布尔型 的条件判断 + - 变量取值:通过 RiskVariableProvider 执行已编译委托 + ⬜ 5. RiskVariableProvider 实现 + - 接收已编译委托 + RiskContext,执行计算并返回变量值 + - 异常捕获与详细错误信息(如"变量 XXX 取值失败:空引用") + ⬜ 6. RiskContext 数据加载(按需加载) + - trade 对象(必须) + - swap_position / client / credit / market / calc / client_marginrate(按需) + - 参考 QuotaMonitorService 中现有查询模式(AsNoTracking, DataCacheProvider) + ⬜ 7. 脚本编译引擎 + - 将变量 ImplementationScript 编译为可执行委托(Expression.Lambda) + - 支持安全表达式求值,杜绝注入风险 + - 按 (id, version) 缓存编译结果 + ⬜ 8. 更多集成点 + - 平仓审核提交(CLOSE_REVIEW):SwapTrade2Controller / unwindSwapTrade.js + - 上传确认书(UPLOAD_CONFIRMATION):ConfirmationGenerateService + - 事件发生时(EVENT_TRIGGER):二期实现 + ⬜ 9. 单元测试 + - 变量计算器取值逻辑 + - 公式解析与执行 + - 策略判定逻辑 + - 完整风控检查流程(API → 结果返回) + ⬜ 10. 前端规则配置 SPA(独立前端仓库) + - 路由:/risk-config(单页面多 Tab:规则管理 / 规则应用 / 变量池 / 操作日志) + ⬜ 11. 前端"确认交易"弹窗展示优化(当前混在"风险预警"栏目中,待独立) + - 问题:风控引擎返回的 Blocked/NeedApproval/Warnings 目前通过 RiskWarningDetails + 映射到前端 quotaTrial.cshtml,统一显示在"风险预警"栏目(橙色表头) + - 方案A(最小改动):前端判断 RiskWarningDetails 是否包含"[风控引擎]"标签, + 单独渲染"风控检查"栏目(如紫色表头),与原有"风险预警"并列展示 + - 方案B(干净做法):QuotaTrial 模型新增 RiskEngineDetails 字段,后端往独立字段写, + 前端新增独立渲染逻辑,彻底分离"风险预警"(旧限额)和"风控检查"(新引擎) + - 相关文件: + → QuotaMonitorService.cs(RiskWarningDetails 赋值逻辑) + → quotaTrial.cshtml(前端渲染,检查类别列"风险预警"写死在前端 HTML) + +【关键文件清单】 + 本模块: + - RiskEngineService.cs(本文件):引擎核心服务 + - RiskContext.cs:风控上下文 + - RiskResult.cs:风控结果 + + 现有集成点: + - QuotaMonitorService.cs(RiskModule 下):QuotaCheck() 末尾已接入 + + 设计文档: + - X:\尹峰\onederiv\风控引擎需求详细设计.md + +【命名空间】 + YLErp.Modules.RiskEngine(与 RiskModule 平级) 【注意事项】 - 1. 当前 RuleExpr 必须是 Roslyn 最终可执行的 C# bool 表达式,不是业务语义短句 - 2. DataMap 中的值由宿主业务代码准备,编译器与执行器本身不负责查库补数 - 3. 当前缓存的是 Func,这是规则判断函数,不是事件处理器 - 4. 业务可预期失败优先走结果返回,不要把高频校验失败都设计成异常 + 1. 本期不对接现有审批模块,规则创建后直接可用(Active) + 2. 旧 QuotaMonitorService 功能保持不变,新旧引擎独立运行 + 3. 变量新增仅需在变量池管理页面添加记录,无需修改引擎核心代码 + 4. 所有查询使用 AsNoTracking() 避免 EF 变更追踪开销 + 5. 数值统一使用 decimal 类型,避免浮点精度问题 ================================================================================ */ @@ -138,55 +185,16 @@ namespace YLErp.Modules.RiskEngine } /// - /// 从内存缓存获取规则列表;缓存为空时兜底加载并填充缓存。 + /// 执行风控检查(第一版:一句简单的 1==1,先跑通) /// - private List GetRules() + public bool Evaluate() { - var rules = _cachedRules; - if (rules != null) - { - return rules; - } - - lock (_cacheLock) - { - if (_cachedRules != null) - { - return _cachedRules; - } - - var loaded = LoadRulesFromDb(); - _cachedRules = loaded; - return loaded; - } + // 第一版占位:脚本 1==1,永远返回 true,验证服务能正常实例化和调用 + return 1 == 1; } /// - /// 从内存缓存获取应用列表;缓存为空时兜底加载并填充缓存。 - /// - private List GetApplications() - { - var applications = _cachedApplications; - if (applications != null) - { - return applications; - } - - lock (_cacheLock) - { - if (_cachedApplications != null) - { - return _cachedApplications; - } - - var loaded = LoadApplicationsFromDb(); - _cachedApplications = loaded; - return loaded; - } - } - - /// - /// 执行风控检查(最终设计版:预编译委托 + 规则筛选 + 策略判定) + /// 执行风控检查(按设计文档预演版) /// /// 风控上下文 /// 触发时点,如 BOOK_CONFIRM @@ -197,369 +205,22 @@ namespace YLErp.Modules.RiskEngine try { - _logger.Info($"[风控引擎] EvaluateRisk 开始 - TradeId: {context?.TradeId}, TriggerPoint: {triggerPoint}"); + // 调试模式:模拟触发审批(NeedApproval),验证前端界面展示 + // TODO: 后续接入真实规则缓存和公式执行逻辑后删除此行 + result.NeedApproval = true; + result.Passed = false; + result.Warnings.Add("【风控引擎】规则触发:需审批 - 模拟审批触发(调试模式)"); - // ============================================================ - // Step 1: 从内存缓存读取规则定义和规则应用(启动时已预热) - // ============================================================ - var rules = GetRules(); - var applications = GetApplications(); - _logger.Info($"[风控引擎] 加载规则数: {rules.Count}, 应用数: {applications.Count}"); - - // ============================================================ - // Step 2: 先按 Application 过滤 Active 状态和 TriggerPoint 匹配的应用 - // ============================================================ - var activeApps = applications - .Where(a => a.Status == RiskRuleStatus.Active) - .ToList(); - var apps = activeApps - .Where(a => !string.IsNullOrEmpty(a.TriggerPoints)) - .ToList(); - var triggerMatchedApps = apps - .Where(a => a.TriggerPoints.Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(s => s.Trim()) - .Contains(triggerPoint)) - .ToList(); - - // 再按应用范围过滤。 - // 统一走通用维度匹配: - // 1. 全局命中时直接通过; - // 2. 同一维度内多选按并集处理; - // 3. 不同维度之间按交集处理; - // 4. 某维度留空表示该维度不限制。 - var trade = context?.DataMap != null && context.DataMap.ContainsKey("trade") - ? context.DataMap["trade"] as YLErp.DBModels.trade - : null; - - var matchedApplications = triggerMatchedApps - .Where(a => IsApplicationMatched(a, trade)) - .ToList(); - - _logger.Info($"[风控引擎] 匹配 TriggerPoint 的应用数: {matchedApplications.Count}"); - - // ============================================================ - // Step 3: 遍历匹配的应用,通过 RuleIds 关联规则并执行预编译委托 - // ============================================================ - foreach (var application in matchedApplications) - { - // ------------------------------------------------------------ - // 3.1 通过 RuleIds 关联规则定义 - // ------------------------------------------------------------ - var applicationRuleIds = ParseRuleIds(application.RuleIds); - if (!applicationRuleIds.Any()) - { - _logger.Info($"[风控引擎] 规则未编译,执行编译 - RuleCode: {rule.RuleCode}"); - RuleCompiler.Compile(rule); - } - - // 执行预编译委托 - bool triggered = false; - try - { - triggered = rule.CompiledScript(context); - _logger.Info($"[风控引擎] 规则执行 - RuleCode: {rule.RuleCode}, Triggered: {triggered}"); - } - catch (Exception ex) - { - _logger.Info($"[风控引擎] 规则执行异常 - RuleCode: {rule.RuleCode}, Error: {ex.Message}"); - result.HasError = true; - result.ErrorMessage += $"规则[{rule.RuleCode}]执行异常:{ex.Message};"; - continue; - } - - var applicationRules = rules - .Where(r => applicationRuleIds.Contains(r.Id) && r.Id == 3) - .ToList(); - - var missingRuleIds = applicationRuleIds - .Where(ruleId => applicationRules.All(r => r.Id != ruleId)) - .ToList(); - - foreach (var missingRuleId in missingRuleIds) - { - _logger.Info($"[风控引擎] 规则触发 - RuleCode: {rule.RuleCode}, Strategy: {rule.ControlStrategy}"); - - foreach (var rule in applicationRules) - { - var ruleId = rule.Id.ToString(); - - // ------------------------------------------------------------ - // 3.2 优先从编译缓存读取规则委托 - // ------------------------------------------------------------ - if (!RuleCompiledCache.TryGet(ruleId, out var compiledScript)) - { - case 1: // 禁止 - result.Blocked = true; - result.Passed = false; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleCode = rule.RuleCode, - RuleName = rule.RuleName, - ControlStrategy = "禁止", - FormulaText = rule.FormulaText, - Message = $"规则[{rule.RuleName}]触发:禁止" - }); - break; - - case 2: // 审批 - result.NeedApproval = true; - result.Passed = false; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleCode = rule.RuleCode, - RuleName = rule.RuleName, - ControlStrategy = "审批", - FormulaText = rule.FormulaText, - Message = $"规则[{rule.RuleName}]触发:需审批" - }); - break; - - case 3: // 提示 - result.Warnings.Add($"规则[{rule.RuleName}]触发:提示 - {rule.FormulaText}"); - break; - - // ------------------------------------------------------------ - // 3.3 执行预编译委托 - // ------------------------------------------------------------ - bool triggered = false; - try - { - triggered = compiledScript(context); - _logger.Info($"[风控引擎] 规则执行 - RuleId: {rule.Id}, Triggered: {triggered}"); - } - catch (Exception ex) - { - _logger.Info($"[风控引擎] 规则执行异常 - RuleId: {rule.Id}, Error: {ex.Message}"); - continue; - } - - // ------------------------------------------------------------ - // 3.4 命中后按 Application 的控制策略聚合结果 - // ------------------------------------------------------------ - if (triggered) - { - _logger.Info($"[风控引擎] 规则触发 - RuleId: {rule.Id}, Strategy: {application.ControlStrategy}"); - - switch (application.ControlStrategy) - { - case RiskControlStrategy.Block: - break; - result.Blocked = true; - result.Passed = false; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleId = rule.Id.ToString(), - RuleName = rule.RuleName, - ControlStrategy = RiskControlStrategy.Block, - RuleText = rule.RuleText, - Message = $"规则[{rule.RuleName}]触发:禁止" - }); - break; - - case RiskControlStrategy.Approval: - result.NeedApproval = true; - result.Passed = false; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleId = ruleId, - RuleName = rule.RuleName, - ControlStrategy = RiskControlStrategy.Approval, - RuleText = rule.RuleText, - Message = $"规则[{rule.RuleName}]触发:需审批" - }); - break; - - case RiskControlStrategy.ShowTip: - result.ShowTip = true; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleId = ruleId, - RuleName = rule.RuleName, - ControlStrategy = RiskControlStrategy.ShowTip, - RuleText = rule.RuleText, - Message = $"规则[{rule.RuleName}]触发:提示" - }); - break; - - default: - _logger.Info($"[风控引擎] 未知策略类型 - ControlStrategy: {application.ControlStrategy}"); - break; - } - } - else - { - _logger.Info($"[风控引擎] 规则未触发 - RuleId: {rule.Id}, TriggerPoint: {triggerPoint}"); - } - } - } - - // ============================================================ - // Step 4: 聚合最终结果 - // ============================================================ - if (!result.Blocked && !result.NeedApproval) - { - result.Passed = true; - } - else - { - result.Passed = false; - } - - _logger.Info($"[风控引擎] EvaluateRisk 完成 - TradeId: {context?.TradeId}, Passed: {result.Passed}, Blocked: {result.Blocked}, NeedApproval: {result.NeedApproval}, TriggeredRules: {result.TriggeredRules.Count}"); + _logger.Info($"[风控引擎] EvaluateRisk 被调用 - TradeId: {context?.TradeId}, TriggerPoint: {triggerPoint}, NeedApproval: {result.NeedApproval}"); } catch (Exception ex) { - result.NeedApproval = true; - result.Passed = false; - result.TriggeredRules.Add(new TriggeredRuleInfo - { - RuleId = "ENGINE_ERROR", - RuleName = "风控引擎执行异常", - ControlStrategy = "审批", - FormulaText = ex.Message, - Message = $"风控引擎异常:{ex.Message},需审批通过" - }); + result.HasError = true; + result.ErrorMessage = $"风控引擎执行异常: {ex.Message}"; _logger.Error($"[风控引擎] EvaluateRisk 异常 - TradeId: {context?.TradeId}, Error: {ex.Message}"); } return result; } - - /// - /// 从数据库加载规则列表 - /// - private List LoadRulesFromDb() - { - // 示例规则:多条件逗号分隔(自动转为 AND) - var rule = new RiskRule - { - Id = 103, - RuleCode = "RISK-20260622-0003", - RuleName = "测试规则:名义本金超过1亿且保证金比例超过50%禁止", - FormulaText = "名义本金>1亿, 保证金比例>50%", // 逗号分隔,解析时自动转为 AND - FormulaJson = "{ \"conditions\": [{\"variableCode\":\"trade.StockEqvNotional\",\"operator\":\">\",\"value\":100000000},{\"variableCode\":\"trade.MarginRate\",\"operator\":\">\",\"value\":0.5}] }", - Status = 1, // Active - ControlStrategy = 1, // 禁止 - TriggerPoints = "BOOK_CONFIRM", - ScopeIsGlobal = true, - Version = 1 - }; - RuleCompiler.Compile(rule); - - /// - /// 判断应用配置是否命中当前交易。 - /// 匹配规则遵循设计文档: - /// 1. 全局命中时直接返回 true; - /// 2. 同一维度内多选按并集处理; - /// 3. 不同维度之间按交集处理; - /// 4. 某维度留空表示该维度不限制。 - /// - private bool IsApplicationMatched(RiskRuleApplication application, YLErp.DBModels.trade trade) - { - if (application == null) - { - return false; - } - - if (application.ScopeIsGlobal) - { - return true; - } - - if (trade == null) - { - return false; - } - - var accountMatched = IsScopeEmpty(application.ScopeAssetBookIds) || IsValueMatched(application.ScopeAssetBookIds, GetTradeAssetBookId(trade)); - var clientMatched = IsScopeEmpty(application.ScopeClientIds) || IsValueMatched(application.ScopeClientIds, trade.ClientId); - var underlyingTypeMatched = IsScopeEmpty(application.ScopeUnderlyingTypes) || IsValueMatched(application.ScopeUnderlyingTypes, GetTradeUnderlyingType(trade)); - var tradeTypeMatched = IsScopeEmpty(application.ScopeTradeTypes) || IsValueMatched(application.ScopeTradeTypes, trade.TradeType); - - return accountMatched - && clientMatched - && underlyingTypeMatched - && tradeTypeMatched; - } - - /// - /// 解析应用配置中的规则ID列表。 - /// 多个规则ID使用逗号分隔,返回去空格后的 long 集合。 - /// - private List ParseRuleIds(string ruleIds) - { - if (string.IsNullOrWhiteSpace(ruleIds)) - { - return new List(); - } - - var ids = new List(); - foreach (var part in ruleIds.Split(',', StringSplitOptions.RemoveEmptyEntries)) - { - if (long.TryParse(part.Trim(), out long id)) - { - ids.Add(id); - } - } - - return ids; - } - - /// - /// 判断某个范围字段是否为空。 - /// 为空表示该维度不限制。 - /// - private bool IsScopeEmpty(string scopeValue) - { - return string.IsNullOrWhiteSpace(scopeValue); - } - - /// - /// 判断单个值是否命中逗号分隔的范围配置。 - /// 同一维度内多选按并集处理,只要命中任一值即返回 true。 - /// - private bool IsValueMatched(string scopeValue, object currentValue) - { - if (string.IsNullOrWhiteSpace(scopeValue)) - { - return true; - } - - if (currentValue == null) - { - return false; - } - - var currentText = currentValue.ToString()?.Trim(); - if (string.IsNullOrWhiteSpace(currentText)) - { - return false; - } - - return scopeValue - .Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(s => s.Trim()) - .Any(s => string.Equals(s, currentText, StringComparison.OrdinalIgnoreCase)); - } - - /// - /// 读取交易的资产簿账户ID。 - /// 当前先按 BookId 取值;如果后续真实字段不是 BookId,再统一调整这里即可。 - /// - private object GetTradeAssetBookId(YLErp.DBModels.trade trade) - { - var property = trade.GetType().GetProperty("BookId"); - return property?.GetValue(trade); - } - - /// - /// 读取交易的标的类型。 - /// 当前先按 UnderlyingInstrumentType 取值;如果后续真实字段名不同,再统一调整这里即可。 - /// - private object GetTradeUnderlyingType(YLErp.DBModels.trade trade) - { - var property = trade.GetType().GetProperty("UnderlyingInstrumentType"); - return property?.GetValue(trade); - } } } diff --git a/YLErpDAL/Modules/RiskEngine/RiskRule.cs b/YLErpDAL/Modules/RiskEngine/RiskRule.cs deleted file mode 100644 index 86b9b09b..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskRule.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控规则定义。 - /// 只描述“规则是什么”,不包含状态、策略、触发时点、适用范围等应用配置。 - /// 这些执行维度由 RiskRuleApplication 承载。 - /// - public class RiskRule - { - // === 基础信息 === - public int Id { get; set; } - public string RuleCode { get; set; } - public string RuleName { get; set; } - public string Description { get; set; } - - /// - /// 【核心】用户输入的规则内容字符串,如"名义本金>1亿" - /// 在规则创建/加载时由 RuleCompiler 解析并编译为 CompiledScript - /// - public string FormulaText { get; set; } - - /// - /// 【核心】公式 JSON(条件列表的结构化存储),如 - /// {"conditions":[{"variableCode":"trade.StockEqvNotional","operator":">","value":100000000}]} - /// 由 RuleCompiler 解析并编译为 CompiledScript - /// - public string FormulaJson { get; set; } - - // === 生命周期 === - public int Version { get; set; } - public bool IsDeleted { get; set; } - - // === 审计字段 === - public int OptId { get; set; } - public string OptName { get; set; } - public DateTime OptDate { get; set; } - public int UpdateOptId { get; set; } - public string UpdateOptName { get; set; } - public DateTime UpdateDate { get; set; } - - // === 运行时内存字段(不持久化到数据库)=== - /// - /// 【核心】预编译的可执行委托 - /// 在规则创建/加载时由 RuleCompiler.Compile(this) 生成 - /// 判风控时直接调用:rule.CompiledScript(context) → bool - /// - public Func CompiledScript { get; set; } - } -} diff --git a/YLErpDAL/YLErpDAL.csproj b/YLErpDAL/YLErpDAL.csproj index e9d409ac..83cf8334 100644 --- a/YLErpDAL/YLErpDAL.csproj +++ b/YLErpDAL/YLErpDAL.csproj @@ -30,8 +30,6 @@ - - @@ -42,7 +40,7 @@ - + From c94a7171837a77a919b048542f99a616bac26b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:14:43 +0800 Subject: [PATCH 16/17] =?UTF-8?q?Revert=20"feat:=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=A3=8E=E6=8E=A7=E5=BC=95=E6=93=8ERiskEngine=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=88=E7=AC=AC=E4=B8=80=E7=89=88=E9=AA=A8=E6=9E=B6?= =?UTF-8?q?=EF=BC=89"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4140293b36667532abaab2126b77c1e3a23da346. --- YLErpDAL/Modules/RiskEngine/RiskContext.cs | 25 -- .../Modules/RiskEngine/RiskEngineService.cs | 226 ------------------ YLErpDAL/Modules/RiskEngine/RiskResult.cs | 47 ---- .../Modules/RiskModule/QuotaMonitorService.cs | 59 ----- 4 files changed, 357 deletions(-) delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskContext.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskEngineService.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskResult.cs diff --git a/YLErpDAL/Modules/RiskEngine/RiskContext.cs b/YLErpDAL/Modules/RiskEngine/RiskContext.cs deleted file mode 100644 index 7e9e550e..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskContext.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控上下文:封装一次风控检查所需的全部数据上下文 - /// - public class RiskContext - { - /// - /// 当前交易ID - /// - public int TradeId { get; set; } - - /// - /// 当前触发时点 - /// - public string TriggerPoint { get; set; } - - /// - /// 数据字典:key=表名/前缀,value=对应数据对象 - /// - public Dictionary DataMap { get; set; } = new Dictionary(); - } -} diff --git a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs b/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs deleted file mode 100644 index c165f109..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskEngineService.cs +++ /dev/null @@ -1,226 +0,0 @@ -using Qdp.Foundation.Utilities; -using YLErp.BLL; -using YLErp.Model; - -/* -================================================================================ - 风控引擎服务 — RiskEngineService 技术方案与当前实现说明 -================================================================================ - -【项目背景】 - 当前 TRS 系统已在 QuotaMonitorService 中接入第一版风控引擎,用于在交易关键 - 时点执行可配置规则判断。当前目标不是一次性做成最终版,而是在最小可运行链路 - 跑通后,逐步演进为可由前端配置、后端预编译、执行期直接命中的正式版本。 - - 本模块旨在将现有限额监控升级为完整的风控引擎,实现规则的灵活配置、公式化执行、 - 策略判定与结果输出。 - -【设计文档】 - 详细设计文档路径: - X:\尹峰\onederiv\风控引擎需求详细设计.md - - 核心概念: - - Rule(规则):定义"什么情况下触发",由变量 + 操作符 + 阈值组成的条件列表 - - Application(应用):定义"何时、对谁、怎么处理",关联规则 + 策略/时点/范围 - - Variable(变量):变量池中的可选项,用于构建规则条件 - - RiskContext:一次风控检查的数据上下文(DataMap 机制) - - RiskResult:风控检查结果(Passed / Blocked / NeedApproval / Warnings) - -【架构设计】 - ┌─────────────────────────────────────────────────────────────────┐ - │ API 层(RiskRuleController / RiskEngineController) │ - │ - 规则 CRUD API、应用配置 CRUD API、变量池 CRUD API │ - │ - 风控执行 API(EvaluateRisk / Trial) │ - └──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────────────────────▼──────────────────────────────────────┐ - │ 风控引擎层(YLErpDAL/Modules/RiskEngine/) ← 本文件夹所在层 │ - │ │ - │ RiskEngineService(本文件) │ - │ - 内存缓存管理(规则 + 应用 + 变量 + 编译后脚本) │ - │ - 构造 RiskContext → 解析 FormulaJson → 执行条件判断 → 输出结果 │ - │ │ - │ RiskVariableProvider(待实现) │ - │ - 接收已编译委托 + RiskContext → 执行计算 → 返回变量值 │ - │ │ - │ RiskRuleService(待实现) │ - │ - 规则 / 应用 / 变量 / 日志的 CRUD │ - │ - CRUD 后触发 RiskEngineService.RefreshCache() │ - │ │ - │ RiskAuditLogService(待实现) │ - │ - 操作日志写入 / 查询 │ - └──────────────────────────┬──────────────────────────────────────┘ - │ - ┌──────────────────────────▼──────────────────────────────────────┐ - │ 数据访问层 │ - │ - RiskRuleRepository(规则 + 应用 + 变量 + 日志) │ - │ - 复用现有 DAL(Trade / Client / SwapPosition / Credit 等) │ - └─────────────────────────────────────────────────────────────────┘ - -【当前代码结构】 - 风控引擎层:YLErpDAL/Modules/RiskEngine/ - - RiskEngineService.cs:执行入口,负责规则筛选、应用过滤、委托执行、结果聚合 - - RiskRule.cs:规则定义模型,含 CompiledScript 运行时字段 - - RiskRuleApplication.cs:规则应用模型,承载策略、触发点和适用范围 - - RiskContext.cs / RiskResult.cs:执行上下文与结果模型 - - 编译相关:YLErpDAL/Modules/RiskEngine/Compile/ - - RuleCompiler.cs:Roslyn 编译入口,提供 ValidateAndCompileFormula / ValidateAndCompileRule - - RuleCompileResult.cs:编译结果模型 - - RuleCompiledCache.cs:编译结果缓存 - - 当前集成点: - - QuotaMonitorService.cs:构造 RiskContext,并将 trade 对象放入 DataMap["trade"] - -【当前进度】(截至 2026-06-24) - ✅ 已完成: - 1. 创建 RiskEngine 文件夹,与 RiskModule 平级,实现代码隔离 - 2. 创建核心模型: - - RiskContext.cs:风控上下文(TradeId, TriggerPoint, DataMap) - - RiskResult.cs:风控结果(Passed, Blocked, NeedApproval, Warnings, TriggeredRules) - 3. RiskEngineService 骨架: - - 继承 YLBaseService,保留三个构造函数 - - Evaluate():1==1 占位方法,验证服务可实例化 - - EvaluateRisk(RiskContext, triggerPoint):按设计文档预演的入口方法 - 4. 集成到现有流程: - - QuotaMonitorService.QuotaCheck() 末尾已插入风控引擎调用 - - 触发时点:BOOK_CONFIRM(簿记交易确认) - - 构造 RiskContext,传入 trade 对象到 DataMap - - 按策略(Blocked/NeedApproval/Warnings)映射回 QuotaTrialStatusEnum - 5. 编译验证通过(YLErpDAL.csproj 0 error, 80 warnings 为旧代码) - -【待办事项 / TODO】(按优先级排序) - ⬜ 1. 内存缓存机制 - - 添加 _cachedRules / _cachedApplications / _cachedVariables / _compiledScripts - - 实现 RefreshCache() 方法,从数据库加载 Active 规则/应用/变量 - - CRUD 操作后即时刷新(由 RiskRuleService 调用) - ⬜ 2. 数据库表创建(SQL) - - glms_risk_rule(规则定义表) - - glms_risk_rule_application(规则应用表) - - glms_risk_variable(变量池定义表) - - glms_risk_rule_audit_log(操作日志表) - ⬜ 3. 规则筛选逻辑 - - 按 TriggerPoint 过滤应用配置 - - 按 Scope(全局/账户/对手方/标的类型/合约类型)过滤适用范围 - - 去重(同一规则可能被多个应用引用) - ⬜ 4. 公式解析与执行 - - 解析 FormulaJson(条件列表,AND 关系) - - 数值型 / 日期型 / 布尔型 的条件判断 - - 变量取值:通过 RiskVariableProvider 执行已编译委托 - ⬜ 5. RiskVariableProvider 实现 - - 接收已编译委托 + RiskContext,执行计算并返回变量值 - - 异常捕获与详细错误信息(如"变量 XXX 取值失败:空引用") - ⬜ 6. RiskContext 数据加载(按需加载) - - trade 对象(必须) - - swap_position / client / credit / market / calc / client_marginrate(按需) - - 参考 QuotaMonitorService 中现有查询模式(AsNoTracking, DataCacheProvider) - ⬜ 7. 脚本编译引擎 - - 将变量 ImplementationScript 编译为可执行委托(Expression.Lambda) - - 支持安全表达式求值,杜绝注入风险 - - 按 (id, version) 缓存编译结果 - ⬜ 8. 更多集成点 - - 平仓审核提交(CLOSE_REVIEW):SwapTrade2Controller / unwindSwapTrade.js - - 上传确认书(UPLOAD_CONFIRMATION):ConfirmationGenerateService - - 事件发生时(EVENT_TRIGGER):二期实现 - ⬜ 9. 单元测试 - - 变量计算器取值逻辑 - - 公式解析与执行 - - 策略判定逻辑 - - 完整风控检查流程(API → 结果返回) - ⬜ 10. 前端规则配置 SPA(独立前端仓库) - - 路由:/risk-config(单页面多 Tab:规则管理 / 规则应用 / 变量池 / 操作日志) - ⬜ 11. 前端"确认交易"弹窗展示优化(当前混在"风险预警"栏目中,待独立) - - 问题:风控引擎返回的 Blocked/NeedApproval/Warnings 目前通过 RiskWarningDetails - 映射到前端 quotaTrial.cshtml,统一显示在"风险预警"栏目(橙色表头) - - 方案A(最小改动):前端判断 RiskWarningDetails 是否包含"[风控引擎]"标签, - 单独渲染"风控检查"栏目(如紫色表头),与原有"风险预警"并列展示 - - 方案B(干净做法):QuotaTrial 模型新增 RiskEngineDetails 字段,后端往独立字段写, - 前端新增独立渲染逻辑,彻底分离"风险预警"(旧限额)和"风控检查"(新引擎) - - 相关文件: - → QuotaMonitorService.cs(RiskWarningDetails 赋值逻辑) - → quotaTrial.cshtml(前端渲染,检查类别列"风险预警"写死在前端 HTML) - -【关键文件清单】 - 本模块: - - RiskEngineService.cs(本文件):引擎核心服务 - - RiskContext.cs:风控上下文 - - RiskResult.cs:风控结果 - - 现有集成点: - - QuotaMonitorService.cs(RiskModule 下):QuotaCheck() 末尾已接入 - - 设计文档: - - X:\尹峰\onederiv\风控引擎需求详细设计.md - -【命名空间】 - YLErp.Modules.RiskEngine(与 RiskModule 平级) - -【注意事项】 - 1. 本期不对接现有审批模块,规则创建后直接可用(Active) - 2. 旧 QuotaMonitorService 功能保持不变,新旧引擎独立运行 - 3. 变量新增仅需在变量池管理页面添加记录,无需修改引擎核心代码 - 4. 所有查询使用 AsNoTracking() 避免 EF 变更追踪开销 - 5. 数值统一使用 decimal 类型,避免浮点精度问题 -================================================================================ -*/ - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控引擎服务(第一版 — 骨架版,先跑通) - /// - public class RiskEngineService : YLBaseService - { - IYcLogger _logger = LogFactory.GetLogger("RiskEngineService"); - public RiskEngineService(OptUserInfo userInfo) : base(userInfo) - { - } - - public RiskEngineService(YLBaseService baseService) : base(baseService) - { - } - - public RiskEngineService(OptUserInfo optUser, YLContext dbContext) : base(optUser, dbContext) - { - } - - /// - /// 执行风控检查(第一版:一句简单的 1==1,先跑通) - /// - public bool Evaluate() - { - // 第一版占位:脚本 1==1,永远返回 true,验证服务能正常实例化和调用 - return 1 == 1; - } - - /// - /// 执行风控检查(按设计文档预演版) - /// - /// 风控上下文 - /// 触发时点,如 BOOK_CONFIRM - /// 风控结果 - public RiskResult EvaluateRisk(RiskContext context, string triggerPoint) - { - var result = new RiskResult(); - - try - { - // 调试模式:模拟触发审批(NeedApproval),验证前端界面展示 - // TODO: 后续接入真实规则缓存和公式执行逻辑后删除此行 - result.NeedApproval = true; - result.Passed = false; - result.Warnings.Add("【风控引擎】规则触发:需审批 - 模拟审批触发(调试模式)"); - - _logger.Info($"[风控引擎] EvaluateRisk 被调用 - TradeId: {context?.TradeId}, TriggerPoint: {triggerPoint}, NeedApproval: {result.NeedApproval}"); - } - catch (Exception ex) - { - result.HasError = true; - result.ErrorMessage = $"风控引擎执行异常: {ex.Message}"; - _logger.Error($"[风控引擎] EvaluateRisk 异常 - TradeId: {context?.TradeId}, Error: {ex.Message}"); - } - - return result; - } - } -} diff --git a/YLErpDAL/Modules/RiskEngine/RiskResult.cs b/YLErpDAL/Modules/RiskEngine/RiskResult.cs deleted file mode 100644 index 7041ac18..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskResult.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Collections.Generic; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控结果 - /// - public class RiskResult - { - /// - /// 是否全部通过 - /// - public bool Passed { get; set; } = true; - - /// - /// 是否被禁止 - /// - public bool Blocked { get; set; } - - /// - /// 是否需要审批 - /// - public bool NeedApproval { get; set; } - - /// - /// 是否存在提示 - /// - public bool ShowTip { get; set; } - - /// - /// 触发的规则列表 - /// - public List TriggeredRules { get; set; } = new List(); - } - - /// - /// 触发规则详情 - /// - public class TriggeredRuleInfo - { - public string RuleId { get; set; } - public string RuleName { get; set; } - public string ControlStrategy { get; set; } - public string FormulaText { get; set; } - public string Message { get; set; } - } -} diff --git a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs index 9dbf410b..1997faf2 100644 --- a/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs +++ b/YLErpDAL/Modules/RiskModule/QuotaMonitorService.cs @@ -50,7 +50,6 @@ using YLErp.Modules.TradeDalModule; using YLErp.Modules.TradeModule; using YLErp.Modules.TradeModule.DealModule; using YLErp.QdpModule; -using YLErp.Modules.RiskEngine; using static YLErp.ConsGlobal; namespace YLErp.Modules.RiskModule @@ -5140,64 +5139,6 @@ namespace YLErp.Modules.RiskModule result.QuotaWarningDetails = quotaWarningMsg ?? ""; result.AvailableForClient = availableMsg ?? ""; result.TrialSource = trialSource; - - // ===== 新风控引擎接入点(设计文档 4.6.1) ===== - try - { - var riskEngine = new RiskEngineService(this); - var riskContext = new RiskContext - { - TradeId = tradeId, - TriggerPoint = "BOOK_CONFIRM" - }; - // 构造 DataMap(第一版只塞 trade 对象,后续按需扩展) - riskContext.DataMap["trade"] = tradeObj; - var riskResult = riskEngine.EvaluateRisk(riskContext, "BOOK_CONFIRM"); - - _logger.Info($"[风控引擎] 簿记交易确认 - TradeId: {tradeId}, Passed: {riskResult.Passed}, Blocked: {riskResult.Blocked}, NeedApproval: {riskResult.NeedApproval}, ShowTip: {riskResult.ShowTip}"); - - // 按策略映射回 QuotaTrialStatusEnum - if (riskResult.Blocked) - { - result.TrialStatus = QuotaTrialStatusEnum.Error; - result.RiskWarningDetails += "[风控引擎] 规则触发:禁止\n"; - foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Block)) - { - result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n"; - } - } - if (riskResult.NeedApproval) - { - if (result.TrialStatus != QuotaTrialStatusEnum.Error) - { - result.TrialStatus = QuotaTrialStatusEnum.Warning; - } - result.RiskWarningDetails += "[风控引擎] 规则触发:需审批\n"; - foreach (var triggeredRule in riskResult.TriggeredRules.Where(r => r.ControlStrategy == RiskControlStrategy.Approval)) - { - result.RiskWarningDetails += $"规则ID:{triggeredRule.RuleId};规则名称:{triggeredRule.RuleName};规则说明:{triggeredRule.RuleText}\n"; - } - } - if (riskResult.ShowTip) - { - var warningMessages = riskResult.TriggeredRules - .Where(r => r.ControlStrategy == RiskControlStrategy.ShowTip) - .Select(r => r.Message) - .Where(r => !string.IsNullOrWhiteSpace(r)) - .ToList(); - if (warningMessages.Count > 0) - { - result.RiskWarningDetails += "[风控引擎] 提示:" + string.Join(";", warningMessages) + "\n"; - } - } - } - catch (Exception ex) - { - _logger.Error($"[风控引擎] 执行异常 - TradeId: {tradeId}, Error: {ex.Message}"); - // 第一版:异常不阻断原有流程,只记录日志 - } - // ===== 新风控引擎接入结束 ===== - _logger.Info($"[限额试算] 试算完成 - tradeId: {tradeId}, TrialStatus: {result.TrialStatus}, FundCheck: {fundStatus}, QuotaCheck: {quotaStatus}"); SaveQuotaTrial(result); return result; From 64299aa7447a56872b501d94c617d5dd09561f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=90=8D=E9=94=90?= <1565842059@qq.com> Date: Thu, 2 Jul 2026 16:19:26 +0800 Subject: [PATCH 17/17] cleanup: remove leftover risk engine references --- .../RiskEngine/Compile/RuleCompileResult.cs | 50 ------ .../RiskEngine/Compile/RuleCompiledCache.cs | 67 --------- .../Modules/RiskEngine/RiskRuleApplication.cs | 142 ------------------ YLErpWeb/App/HostedTaskService.cs | 15 +- 4 files changed, 1 insertion(+), 273 deletions(-) delete mode 100644 YLErpDAL/Modules/RiskEngine/Compile/RuleCompileResult.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/Compile/RuleCompiledCache.cs delete mode 100644 YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs diff --git a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompileResult.cs b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompileResult.cs deleted file mode 100644 index 65a88901..00000000 --- a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompileResult.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 规则编译结果。 - /// 用于承载公式校验、Roslyn 编译结果和错误信息。 - /// - public class RuleCompileResult - { - /// - /// 是否编译成功。 - /// - public bool Success { get; set; } - - /// - /// 编译失败时的错误信息。 - /// - public string ErrorMessage { get; set; } - - /// - /// 编译成功后的可执行委托。 - /// - public Func CompiledScript { get; set; } - - /// - /// 创建成功结果。 - /// - public static RuleCompileResult Ok(Func compiledScript) - { - return new RuleCompileResult - { - Success = true, - CompiledScript = compiledScript - }; - } - - /// - /// 创建失败结果。 - /// - public static RuleCompileResult Fail(string errorMessage) - { - return new RuleCompileResult - { - Success = false, - ErrorMessage = errorMessage - }; - } - } -} \ No newline at end of file diff --git a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiledCache.cs b/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiledCache.cs deleted file mode 100644 index 61fcd263..00000000 --- a/YLErpDAL/Modules/RiskEngine/Compile/RuleCompiledCache.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Concurrent; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 规则编译结果的进程内缓存。 - /// 当前第一版按规则 Id 缓存 Roslyn 编译后的可执行委托。 - /// - public static class RuleCompiledCache - { - /// - /// 规则编译结果的进程内缓存。ConcurrentDictionary为线程安全的字典,用于缓存编译后的规则委托,性能略低。 - /// - private static readonly ConcurrentDictionary> Cache - = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); - - /// - /// 写入或更新规则编译结果。 - /// - public static void Set(string ruleCode, Func compiledScript) - { - if (string.IsNullOrWhiteSpace(ruleCode) || compiledScript == null) - { - return; - } - - Cache[ruleCode] = compiledScript; - } - - /// - /// 尝试获取规则编译结果。 - /// - public static bool TryGet(string ruleCode, out Func compiledScript) - { - compiledScript = null; - - if (string.IsNullOrWhiteSpace(ruleCode)) - { - return false; - } - - return Cache.TryGetValue(ruleCode, out compiledScript); - } - - /// - /// 删除指定规则的编译结果。 - /// - public static void Remove(string ruleCode) - { - if (string.IsNullOrWhiteSpace(ruleCode)) - { - return; - } - - Cache.TryRemove(ruleCode, out _); - } - - /// - /// 清空全部规则编译结果。 - /// - public static void Clear() - { - Cache.Clear(); - } - } -} \ No newline at end of file diff --git a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs b/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs deleted file mode 100644 index 7b741252..00000000 --- a/YLErpDAL/Modules/RiskEngine/RiskRuleApplication.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using YLErp.DBModels; - -namespace YLErp.Modules.RiskEngine -{ - /// - /// 风控规则应用配置。 - /// - /// 设计原因: - /// 依据设计文档,风控规则需要区分“规则定义”和“规则应用”两个层面。 - /// RiskRule 负责定义“规则是什么”,例如规则名称、规则编码、公式表达式、描述、版本等; - /// RiskRuleApplication 负责定义“规则在什么场景下如何执行”,例如启用状态、校验策略、触发时点、适用范围。 - /// - /// 拆分后的好处是: - /// 1. 同一条规则可以被多个应用配置复用,避免重复维护相同的公式定义。 - /// 2. 不同业务场景可以对同一条规则配置不同的执行策略,例如在簿记确认时禁止,在平仓审核时提示。 - /// 3. 调整触发时点、适用范围、控制策略时,不需要修改规则本身,降低配置耦合。 - /// 4. 后续如果要做规则管理页面、规则启停、按场景灰度投放,也更容易扩展。 - /// - /// 当前文件先定义第一版最小模型,用于支撑基础流程跑通和示例数据加载。 - /// - public class RiskRuleApplication - { - /// - /// 应用配置主键ID。 - /// 用于唯一标识一条规则应用记录。 - /// - public int Id { get; set; } - - /// - /// 关联的规则ID列表。 - /// 通过该字段与 RiskRule.Id 关联,多个规则ID使用逗号分隔。 - /// - - public string RuleIds { get; set; } - - /// - /// 应用状态。 - /// 约定: - /// 1 = Active(已生效) - /// 2 = Disabled(已停用) - /// 3 = Deleted(已删除) - /// 在实际执行时,通常只加载 Active 状态的应用配置。 - /// - public RiskRuleStatus Status { get; set; } - - /// - /// 校验策略。 - /// 约定: - /// 1 = 禁止 - /// 2 = 审批 - /// 3 = 提示 - /// 当规则命中时,由该字段决定最终处理方式。 - /// - public RiskControlStrategy ControlStrategy { get; set; } - - /// - /// 触发时点。 - /// 多个时点使用逗号分隔,例如: - /// BOOK_CONFIRM,CLOSE_REVIEW - /// 表示该应用配置会在这些业务时点参与风控判断。 - /// - public string TriggerPoints { get; set; } - - /// - /// 是否全局适用。 - /// true 表示对所有交易都适用,此时通常忽略其他 Scope 字段; - /// false 表示只在指定范围内适用,需要结合下方各 Scope 字段进行过滤。 - /// - public bool ScopeIsGlobal { get; set; } - - /// - /// 适用的资产簿记账户范围。 - /// 第一版使用逗号分隔的字符串保存,例如: - /// 123,456,789 - /// 后续执行时可按约定解析为账户ID集合。 - /// - public string ScopeAssetBookIds { get; set; } - - /// - /// 适用的客户范围。 - /// 第一版使用逗号分隔的字符串保存客户ID,例如: - /// 10001,10002 - /// 用于在执行前筛选该规则是否适用于当前客户。 - /// - public string ScopeClientIds { get; set; } - - /// - /// 适用的标的类型范围。 - /// 例如: - /// 股票,ETF,债券 - /// 用于控制规则仅对特定标的类型生效。 - /// - public string ScopeUnderlyingTypes { get; set; } - - /// - /// 适用的交易类型范围。 - /// 例如: - /// 簿记交易,平仓交易 - /// 用于控制规则仅在指定交易类型下生效。 - /// - public string ScopeTradeTypes { get; set; } - - public int Version { get; set; } - - /// - /// 创建人ID。 - /// 记录是谁创建了这条应用配置。 - /// - public int OptId { get; set; } - - /// - /// 创建人名称。 - /// 便于界面展示和审计追踪。 - /// - public string OptName { get; set; } - - /// - /// 创建时间。 - /// 记录应用配置的创建时刻。 - /// - public DateTime OptDate { get; set; } - - /// - /// 最后更新人ID。 - /// 记录最近一次修改该应用配置的操作人。 - /// - public int UpdateOptId { get; set; } - - /// - /// 最后更新人名称。 - /// 便于界面展示和审计追踪。 - /// - public string UpdateOptName { get; set; } - - /// - /// 最后更新时间。 - /// 记录最近一次修改该应用配置的时间。 - /// - public DateTime UpdateDate { get; set; } - } -} diff --git a/YLErpWeb/App/HostedTaskService.cs b/YLErpWeb/App/HostedTaskService.cs index 28660c10..75bf1339 100644 --- a/YLErpWeb/App/HostedTaskService.cs +++ b/YLErpWeb/App/HostedTaskService.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Hosting.Server.Features; using YLErp.DBModels.Consts; using YLErp.Modules.AppModule; using YLErp.Modules.EodModule.SettlementModule; -using YLErp.Modules.RiskEngine; using YLErp.Modules.RiskModule; using YLErp.Modules.SuperviseReportModule.SAC.Service; using YLErp.Modules.TradeRiskCalcModule; @@ -125,18 +124,6 @@ namespace YLErp.Web.App PS.SetConfig(ConsAppConfig.YLErpWebUrlConfig, address); } - // 预热风控引擎:加载规则与应用到内存,并预编译所有规则 - Task.Run(() => - { - try - { - RiskEngineService.GetInstance().Preload(); - } - catch (Exception ex) - { - LogFactory.GetLogger("HostedTaskService").Error("[风控引擎] 启动预热失败", ex); - } - }); }); return Task.CompletedTask; @@ -157,4 +144,4 @@ namespace YLErp.Web.App return Task.CompletedTask; } } -} \ No newline at end of file +}