using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Scripting;
using Newtonsoft.Json;
using System;
using System.Linq;
using YLErp.BLL;
using YLErp.QdpModule;
namespace YLErp.Modules.RiskEngine
{
///
/// Roslyn 脚本全局变量容器
///
public class ScriptGlobals
{
///
/// 当前交易ID
///
public int TradeId { get; set; }
///
/// 当前触发时点
///
public string TriggerPoint { get; set; }
///
/// 数据库上下文,供规则公式直接查询数据库
///
public YLContext DbContext { get; set; }
}
///
/// 规则编译器(Roslyn 版)
///
/// 编译时机:规则定义时(零运行时解析开销)
/// 执行方式:判风控时直接调用 rule.CompiledScript(context) → bool
///
/// 相比表达式树方案的优势:
/// - 代码简洁:生成 C# 字符串即可,无需手动拼接 Expression 节点
/// - 灵活性高:天然支持多条件 AND/OR、复杂运算、未来可扩展任意 C# 语法
/// - 可调试:生成的脚本代码可直接阅读和理解
///
/// 示例:
/// RuleExpr: Convert.ToDecimal(DbContext.trade.First(t => t.id == TradeId).StockEqvNotional) > 100000000m
/// 编译结果:Func<RiskContext, bool>(内部通过 Roslyn 编译缓存)
///
public static class RuleCompiler
{
private static IYcLogger _logger = LogFactory.GetLogger("RuleCompiler");
///
/// 高风险方法黑名单。
/// 这里按方法名做语法层拦截,覆盖数据库写入、原生 SQL、反射、文件、进程、服务定位等入口。
///
private static readonly HashSet ForbiddenInvocationNames = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"SaveChanges",
"SaveChangesAsync",
"Add",
"AddAsync",
"AddRange",
"AddRangeAsync",
"AddOrUpdate",
"Update",
"UpdateRange",
"Remove",
"RemoveRange",
"Attach",
"AttachRange",
"Set",
"Entry",
"ExecuteSqlRaw",
"ExecuteSqlRawAsync",
"ExecuteSqlInterpolated",
"ExecuteSqlInterpolatedAsync",
"ExecuteSql",
"ExecuteSqlAsync",
"ExecuteSqlCommand",
"ExecuteSqlCommandAsync",
"ExecuteStoreCommand",
"ExecuteStoreQuery",
"ExecuteDelete",
"ExecuteDeleteAsync",
"ExecuteUpdate",
"ExecuteUpdateAsync",
"FromSqlRaw",
"FromSqlRawAsync",
"FromSqlInterpolated",
"FromSqlInterpolatedAsync",
"BulkInsert",
"BulkUpdate",
"BulkDelete",
"BulkMerge",
"GetType",
"Invoke",
"InvokeMember",
"GetMethod",
"GetProperty",
"GetField",
"CreateInstance",
"Load",
"LoadFrom",
"ReadAllText",
"ReadAllLines",
"ReadAllBytes",
"WriteAllText",
"WriteAllLines",
"WriteAllBytes",
"AppendAllText",
"Delete",
"Move",
"Copy",
"Open",
"OpenRead",
"OpenWrite",
"Create",
"CreateText",
"Start",
"Kill",
"Exit",
"GetEnvironmentVariable",
"SetEnvironmentVariable",
"GetService",
"GetRequiredService",
"CreateScope",
"Sleep"
};
///
/// 高风险成员黑名单。
/// DbContext.Database、ChangeTracker 等成员会绕过只读查询约束或暴露底层状态,禁止脚本访问。
///
private static readonly HashSet ForbiddenMemberNames = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"Database",
"ChangeTracker",
"Assembly",
"AppDomain"
};
///
/// 高风险类型或标识符黑名单。
/// 用于拦截 File、Process、Environment 等直接作为类型或变量名出现的绕过方式。
///
private static readonly HashSet ForbiddenTypeOrIdentifierNames = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"Activator",
"Assembly",
"AppDomain",
"Environment",
"File",
"Directory",
"Path",
"Process",
"HttpClient",
"WebClient",
"WebRequest",
"Socket",
"Thread",
"GC"
};
///
/// 校验并编译规则表达式。
/// 当前 RuleExpr 要求是 Roslyn 可直接执行的 bool 表达式。
/// ruleId 可为空,用于规则未落库时的公式校验。
///
public static RuleCompileResult ValidateAndCompileFormula(long? ruleId, string formulaExp)
{
var ruleIdText = ruleId.HasValue ? ruleId.Value.ToString() : "未落库";
if (string.IsNullOrWhiteSpace(formulaExp))
{
_logger.Error($"规则表达式为空,无法编译 - RuleId: {ruleIdText}");
return RuleCompileResult.Fail("规则 RuleExpr 不能为空");
}
try
{
var compiled = CompileScript(ruleId, formulaExp, out string compileErrorMessage);
if (compiled == null)
{
return RuleCompileResult.Fail(compileErrorMessage ?? "脚本编译失败");
}
return RuleCompileResult.Ok(compiled);
}
catch (Exception ex)
{
_logger.Error($"规则表达式校验异常 - RuleId: {ruleIdText}, Error: {ex.Message}\n脚本代码:{formulaExp}");
return RuleCompileResult.Fail(ex.Message);
}
}
///
/// 校验变量取值表达式。
/// 变量表达式只要求能编译为 object,不在保存时执行,避免依赖真实交易和数据库数据。
///
public static RuleCompileResult ValidateVariableExpression(string variableExpr)
{
if (string.IsNullOrWhiteSpace(variableExpr))
return RuleCompileResult.Fail("变量取值表达式不能为空");
var compiled = CompileVariableExpression(variableExpr, out var compileErrorMessage);
if (compiled == null)
return RuleCompileResult.Fail(compileErrorMessage ?? "变量取值表达式编译失败");
return RuleCompileResult.Ok(null);
}
///
/// 编译变量取值表达式。
/// 结构化规则执行时使用,表达式返回值由执行器按变量 DataType 统一转换。
///
public static Func CompileValueExpression(string variableExpr, out string errorMessage)
{
if (string.IsNullOrWhiteSpace(variableExpr))
{
errorMessage = "变量取值表达式不能为空";
return null;
}
return CompileVariableExpression(variableExpr, out errorMessage);
}
///
/// 编译变量取值表达式。优先兼容原有单表达式写法;失败后再按多语句脚本编译。
/// 多语句脚本支持用 return 表达最终值,编译前会转换为 Roslyn Script 稳定支持的末尾表达式。
///
private static Func CompileVariableExpression(string variableExpr, out string errorMessage)
{
if (IsLikelyVariableScript(variableExpr))
{
var normalizedScript = NormalizeVariableScript(variableExpr);
var scriptCompiled = CompileScript