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