Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/AdvancedRuleEngineService.cs
2026-04-06 22:04:05 +08:00

891 lines
32 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrpaonVision.Core.Results;
using OrpaonVision.SiteApp.Runtime.Contracts;
using OrpaonVision.SiteApp.Runtime.Options;
using System.Text.Json;
namespace OrpaonVision.SiteApp.Runtime.Services;
/// <summary>
/// 高级规则引擎服务实现。
/// 支持复杂规则配置、多条件判断、动态规则加载等功能。
/// </summary>
public sealed class AdvancedRuleEngineService : IRuleEngineService, IDisposable
{
private readonly ILogger<AdvancedRuleEngineService> _logger;
private readonly RuntimeOptions _options;
private readonly Dictionary<string, RuleDefinition> _rules;
private readonly object _lockObject = new();
private bool _isInitialized;
/// <summary>
/// 构造函数。
/// </summary>
public AdvancedRuleEngineService(ILogger<AdvancedRuleEngineService> logger, IOptions<RuntimeOptions> options)
{
_logger = logger;
_options = options.Value;
_rules = new Dictionary<string, RuleDefinition>();
InitializeDefaultRules();
}
/// <inheritdoc />
public Result<RuntimeDecisionDto> Evaluate(int currentLayer, InferenceResultDto inference)
{
try
{
_logger.LogDebug("执行规则引擎评估:当前层={CurrentLayer},推理结果={Label},置信度={Confidence:F3}",
currentLayer, inference.Label, inference.Confidence);
if (!_isInitialized)
{
_logger.LogWarning("规则引擎未初始化,使用默认规则");
return EvaluateWithDefaultRule(currentLayer, inference);
}
// 获取当前层的规则
var layerRules = GetRulesForLayer(currentLayer);
if (layerRules.Count == 0)
{
_logger.LogWarning("第 {CurrentLayer} 层没有配置规则,使用默认规则", currentLayer);
return EvaluateWithDefaultRule(currentLayer, inference);
}
// 执行规则评估
var evaluationContext = new RuleEvaluationContext
{
CurrentLayer = currentLayer,
Inference = inference,
Timestamp = DateTime.UtcNow
};
var results = new List<RuleEvaluationResult>();
foreach (var rule in layerRules)
{
var result = EvaluateRule(rule, evaluationContext);
results.Add(result);
_logger.LogDebug("规则 {RuleName} 评估结果:{IsPass} - {Message}",
rule.Name, result.IsPass, result.Message);
// 如果是关键规则且失败,可以提前终止
if (!result.IsPass && rule.IsCritical)
{
_logger.LogInformation("关键规则 {RuleName} 失败,提前终止评估", rule.Name);
break;
}
}
// 汇总评估结果
var finalDecision = AggregateEvaluationResults(results, evaluationContext);
_logger.LogInformation("规则引擎评估完成:{DecisionCode} - {DecisionMessage}",
finalDecision.Code, finalDecision.Message);
return Result<RuntimeDecisionDto>.Success(finalDecision, message: "规则判定完成");
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "规则引擎评估失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "RULE_ENGINE_EVALUATION_FAILED", "规则引擎评估失败", traceId);
return Result<RuntimeDecisionDto>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <summary>
/// 初始化默认规则。
/// </summary>
private void InitializeDefaultRules()
{
lock (_lockObject)
{
try
{
_logger.LogInformation("正在初始化默认规则...");
// 添加基础缺陷检测规则
var defectRule = new RuleDefinition
{
Name = "DefectDetection",
Description = "缺陷检测规则",
Layer = 0, // 适用于所有层
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.LabelEquals,
Parameter = "defect",
Operator = "equals",
ExpectedValue = "defect"
},
new()
{
Type = ConditionType.ConfidenceThreshold,
Parameter = "confidence",
Operator = ">=",
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "检测到缺陷",
["severity"] = "high"
}
},
IsCritical = true,
Priority = 1,
Enabled = true
};
// 添加置信度规则
var confidenceRule = new RuleDefinition
{
Name = "ConfidenceCheck",
Description = "置信度检查规则",
Layer = 0, // 适用于所有层
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.ConfidenceThreshold,
Parameter = "confidence",
Operator = "<",
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "置信度过低",
["severity"] = "medium"
}
},
IsCritical = false,
Priority = 2,
Enabled = true
};
// 添加正常通过规则
var passRule = new RuleDefinition
{
Name = "NormalPass",
Description = "正常通过规则",
Layer = 0, // 适用于所有层
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.LabelEquals,
Parameter = "normal",
Operator = "equals",
ExpectedValue = "normal"
},
new()
{
Type = ConditionType.ConfidenceThreshold,
Parameter = "confidence",
Operator = ">=",
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsOK,
Parameters = new Dictionary<string, object>
{
["reason"] = "正常检测"
}
},
IsCritical = false,
Priority = 3,
Enabled = true
};
// 添加数量检查规则
var quantityRule = new RuleDefinition
{
Name = "QuantityCheck",
Description = "部件数量检查规则",
Layer = 0, // 适用于所有层
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.QuantityCheck,
Parameter = "",
Operator = "equals",
ExpectedValue = "3-5" // 期望3-5个部件
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "部件数量不符合要求",
["severity"] = "medium"
}
},
IsCritical = false,
Priority = 4,
Enabled = true
};
// 添加位置检查规则
var positionRule = new RuleDefinition
{
Name = "PositionCheck",
Description = "部件位置检查规则",
Layer = 0,
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.PositionCheck,
Parameter = "screw", // 检查螺丝位置
Operator = "within",
ExpectedValue = "100,100,50" // 圆形区域:中心(100,100)半径50
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "部件位置偏离",
["severity"] = "high"
}
},
IsCritical = true,
Priority = 5,
Enabled = true
};
// 添加到位检查规则
var placementRule = new RuleDefinition
{
Name = "PlacementCheck",
Description = "部件到位检查规则",
Layer = 0,
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.PlacementCheck,
Parameter = "",
Operator = "equals",
ExpectedValue = "bracket=2" // 期望2个支架
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "部件未完全到位",
["severity"] = "medium"
}
},
IsCritical = false,
Priority = 6,
Enabled = true
};
// 添加禁装检查规则
var forbiddenRule = new RuleDefinition
{
Name = "ForbiddenCheck",
Description = "禁装部件检查规则",
Layer = 0,
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.ForbiddenCheck,
Parameter = "",
Operator = "not_contains",
ExpectedValue = "wrong_part,temp_part" // 禁止错误的部件和临时部件
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "发现禁装部件",
["severity"] = "high"
}
},
IsCritical = true,
Priority = 7,
Enabled = true
};
// 添加顺序检查规则
var sequenceRule = new RuleDefinition
{
Name = "SequenceCheck",
Description = "装配顺序检查规则",
Layer = 0,
Conditions = new List<RuleCondition>
{
new()
{
Type = ConditionType.SequenceCheck,
Parameter = "",
Operator = "equals",
ExpectedValue = "base>bracket>screw>cover" // 期望装配顺序
}
},
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "装配顺序错误",
["severity"] = "high"
}
},
IsCritical = true,
Priority = 8,
Enabled = true
};
_rules["DefectDetection"] = defectRule;
_rules["ConfidenceCheck"] = confidenceRule;
_rules["NormalPass"] = passRule;
_rules["QuantityCheck"] = quantityRule;
_rules["PositionCheck"] = positionRule;
_rules["PlacementCheck"] = placementRule;
_rules["ForbiddenCheck"] = forbiddenRule;
_rules["SequenceCheck"] = sequenceRule;
_isInitialized = true;
_logger.LogInformation("默认规则初始化完成,共加载 {Count} 条规则", _rules.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "初始化默认规则失败");
}
}
}
/// <summary>
/// 获取指定层的规则。
/// </summary>
private List<RuleDefinition> GetRulesForLayer(int layer)
{
return _rules.Values
.Where(rule => rule.Layer == 0 || rule.Layer == layer)
.Where(rule => rule.Enabled)
.OrderBy(rule => rule.Priority)
.ToList();
}
/// <summary>
/// 评估单个规则。
/// </summary>
private RuleEvaluationResult EvaluateRule(RuleDefinition rule, RuleEvaluationContext context)
{
try
{
var conditionResults = new List<bool>();
foreach (var condition in rule.Conditions)
{
var result = EvaluateCondition(condition, context);
conditionResults.Add(result);
// 如果是AND逻辑且有一个条件失败可以提前退出
if (!result && rule.ConditionLogic == ConditionLogic.And)
{
break;
}
}
// 根据逻辑运算符确定最终结果
var isPass = rule.ConditionLogic == ConditionLogic.And
? conditionResults.All(r => r)
: conditionResults.Any(r => r);
var message = isPass
? $"规则 {rule.Name} 通过"
: $"规则 {rule.Name} 失败: {string.Join(", ", rule.Conditions.Select(c => c.Type))}";
return new RuleEvaluationResult
{
RuleName = rule.Name,
IsPass = isPass,
Message = message,
Action = isPass ? null : rule.Action,
ExecutionTime = DateTime.UtcNow
};
}
catch (Exception ex)
{
_logger.LogError(ex, "评估规则 {RuleName} 失败", rule.Name);
return new RuleEvaluationResult
{
RuleName = rule.Name,
IsPass = false,
Message = $"规则评估异常: {ex.Message}",
Action = new RuleAction
{
Type = ActionType.MarkAsNG,
Parameters = new Dictionary<string, object>
{
["reason"] = "规则评估异常",
["severity"] = "high"
}
},
ExecutionTime = DateTime.UtcNow
};
}
}
/// <summary>
/// 评估条件。
/// </summary>
private bool EvaluateCondition(RuleCondition condition, RuleEvaluationContext context)
{
return condition.Type switch
{
ConditionType.LabelEquals => EvaluateLabelEquals(condition, context),
ConditionType.ConfidenceThreshold => EvaluateConfidenceThreshold(condition, context),
ConditionType.QuantityCheck => EvaluateQuantityCheck(condition, context),
ConditionType.PositionCheck => EvaluatePositionCheck(condition, context),
ConditionType.PlacementCheck => EvaluatePlacementCheck(condition, context),
ConditionType.ForbiddenCheck => EvaluateForbiddenCheck(condition, context),
ConditionType.SequenceCheck => EvaluateSequenceCheck(condition, context),
_ => false
};
}
/// <summary>
/// 评估标签等于条件。
/// </summary>
private bool EvaluateLabelEquals(RuleCondition condition, RuleEvaluationContext context)
{
var expectedLabel = condition.ExpectedValue;
var actualLabel = context.Inference.Label;
return condition.Operator switch
{
"equals" => actualLabel == expectedLabel,
"not_equals" => actualLabel != expectedLabel,
"contains" => actualLabel.Contains(expectedLabel),
"not_contains" => !actualLabel.Contains(expectedLabel),
_ => actualLabel == expectedLabel
};
}
/// <summary>
/// 评估置信度阈值条件。
/// </summary>
private bool EvaluateConfidenceThreshold(RuleCondition condition, RuleEvaluationContext context)
{
if (!decimal.TryParse(condition.ExpectedValue, out var threshold))
{
_logger.LogWarning("置信度阈值解析失败: {Value}", condition.ExpectedValue);
return false;
}
var actualConfidence = (decimal)context.Inference.Confidence;
return condition.Operator switch
{
">=" => actualConfidence >= threshold,
"<=" => actualConfidence <= threshold,
">" => actualConfidence > threshold,
"<" => actualConfidence < threshold,
"==" => actualConfidence == threshold,
"!=" => actualConfidence != threshold,
_ => actualConfidence >= threshold
};
}
/// <summary>
/// 评估数量检查条件。
/// </summary>
private bool EvaluateQuantityCheck(RuleCondition condition, RuleEvaluationContext context)
{
try
{
// 解析期望数量范围,格式如 "min-max" 或 "exact"
var expectedRange = condition.ExpectedValue;
var actualCount = context.Inference.Detections.Count;
_logger.LogDebug("数量检查:期望={ExpectedRange},实际={ActualCount}", expectedRange, actualCount);
if (expectedRange.Contains('-'))
{
// 范围检查格式min-max
var parts = expectedRange.Split('-');
if (parts.Length == 2 && int.TryParse(parts[0], out var min) && int.TryParse(parts[1], out var max))
{
return actualCount >= min && actualCount <= max;
}
}
else if (int.TryParse(expectedRange, out var exact))
{
// 精确数量检查
return actualCount == exact;
}
_logger.LogWarning("数量检查条件解析失败: {Value}", expectedRange);
return false;
}
catch (Exception ex)
{
_logger.LogError(ex, "数量检查评估失败");
return false;
}
}
/// <summary>
/// 评估位置检查条件。
/// </summary>
private bool EvaluatePositionCheck(RuleCondition condition, RuleEvaluationContext context)
{
try
{
// 解析期望位置,格式如 "x_min:x_max,y_min:y_max" 或 "center_x,center_y,radius"
var expectedPosition = condition.ExpectedValue;
var detections = context.Inference.Detections.Where(d => d.ClassName == condition.Parameter || string.IsNullOrEmpty(condition.Parameter)).ToList();
if (!detections.Any())
{
_logger.LogDebug("位置检查:未找到匹配的检测对象");
return false;
}
// 检查是否所有检测对象都在期望位置范围内
foreach (var detection in detections)
{
var isInRange = CheckPositionInRange(detection.CenterX, detection.CenterY, expectedPosition);
if (!isInRange)
{
_logger.LogDebug("位置检查失败:检测对象 {ClassName} 位置 ({X}, {Y}) 不在期望范围内 {Range}",
detection.ClassName, detection.CenterX, detection.CenterY, expectedPosition);
return false;
}
}
_logger.LogDebug("位置检查通过:所有 {Count} 个检测对象都在期望范围内", detections.Count);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "位置检查评估失败");
return false;
}
}
/// <summary>
/// 评估到位检查条件。
/// </summary>
private bool EvaluatePlacementCheck(RuleCondition condition, RuleEvaluationContext context)
{
try
{
// 解析期望的到位状态格式className=expected_count
var placementRule = condition.ExpectedValue;
var parts = placementRule.Split('=');
if (parts.Length != 2 || !int.TryParse(parts[1], out var expectedCount))
{
_logger.LogWarning("到位检查条件解析失败: {Value}", placementRule);
return false;
}
var targetClass = parts[0];
var actualCount = context.Inference.Detections.Count(d => d.ClassName == targetClass);
_logger.LogDebug("到位检查:{ClassName} 期望={ExpectedCount},实际={ActualCount}",
targetClass, expectedCount, actualCount);
return actualCount >= expectedCount;
}
catch (Exception ex)
{
_logger.LogError(ex, "到位检查评估失败");
return false;
}
}
/// <summary>
/// 评估禁装检查条件。
/// </summary>
private bool EvaluateForbiddenCheck(RuleCondition condition, RuleEvaluationContext context)
{
try
{
// 检查是否存在禁用的部件
var forbiddenClasses = condition.ExpectedValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
var detections = context.Inference.Detections;
foreach (var forbiddenClass in forbiddenClasses)
{
var forbiddenDetections = detections.Where(d => d.ClassName.Equals(forbiddenClass.Trim(), StringComparison.OrdinalIgnoreCase)).ToList();
if (forbiddenDetections.Any())
{
_logger.LogDebug("禁装检查失败:发现禁用部件 {ClassName},数量={Count}", forbiddenClass, forbiddenDetections.Count);
return false; // 发现禁用部件,检查失败
}
}
_logger.LogDebug("禁装检查通过:未发现禁用部件");
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "禁装检查评估失败");
return false;
}
}
/// <summary>
/// 评估顺序检查条件。
/// </summary>
private bool EvaluateSequenceCheck(RuleCondition condition, RuleEvaluationContext context)
{
try
{
// 解析期望顺序,格式如 "class1>class2>class3"
var expectedSequence = condition.ExpectedValue;
var sequenceParts = expectedSequence.Split('>', StringSplitOptions.RemoveEmptyEntries);
// 按Y坐标排序检测对象假设装配顺序是从上到下
var sortedDetections = context.Inference.Detections
.Where(d => sequenceParts.Contains(d.ClassName))
.OrderBy(d => d.CenterY)
.Select(d => d.ClassName)
.ToList();
var actualSequence = string.Join(">", sortedDetections);
var isCorrectOrder = actualSequence.Equals(expectedSequence, StringComparison.OrdinalIgnoreCase);
_logger.LogDebug("顺序检查:期望={Expected},实际={Actual},结果={Result}",
expectedSequence, actualSequence, isCorrectOrder ? "通过" : "失败");
return isCorrectOrder;
}
catch (Exception ex)
{
_logger.LogError(ex, "顺序检查评估失败");
return false;
}
}
/// <summary>
/// 检查位置是否在指定范围内。
/// </summary>
private static bool CheckPositionInRange(float centerX, float centerY, string rangeDefinition)
{
if (rangeDefinition.Contains(','))
{
// 圆形范围检查格式center_x,center_y,radius
var parts = rangeDefinition.Split(',');
if (parts.Length == 3 &&
float.TryParse(parts[0], out var expectedCenterX) &&
float.TryParse(parts[1], out var expectedCenterY) &&
float.TryParse(parts[2], out var radius))
{
var distance = Math.Sqrt(Math.Pow(centerX - expectedCenterX, 2) + Math.Pow(centerY - expectedCenterY, 2));
return distance <= radius;
}
}
else if (rangeDefinition.Contains(':'))
{
// 矩形范围检查格式x_min:x_max,y_min:y_max
var coordinates = rangeDefinition.Split(',');
if (coordinates.Length == 2)
{
var xRange = coordinates[0].Split(':');
var yRange = coordinates[1].Split(':');
if (xRange.Length == 2 && yRange.Length == 2 &&
float.TryParse(xRange[0], out var xMin) &&
float.TryParse(xRange[1], out var xMax) &&
float.TryParse(yRange[0], out var yMin) &&
float.TryParse(yRange[1], out var yMax))
{
return centerX >= xMin && centerX <= xMax && centerY >= yMin && centerY <= yMax;
}
}
}
return false;
}
/// <summary>
/// 汇总评估结果。
/// </summary>
private RuntimeDecisionDto AggregateEvaluationResults(List<RuleEvaluationResult> results, RuleEvaluationContext context)
{
var failedRules = results.Where(r => !r.IsPass).ToList();
var criticalFailures = failedRules.Where(r => r.Action?.Parameters?.ContainsKey("severity") == true &&
r.Action.Parameters["severity"].ToString() == "high").ToList();
var isPass = failedRules.Count == 0;
var decisionCode = isPass ? "RULE_PASS" : "RULE_NG";
var decisionMessage = isPass
? $"第 {context.CurrentLayer} 层所有规则通过"
: $"第 {context.CurrentLayer} 层规则失败: {string.Join(", ", failedRules.Select(r => r.RuleName))}";
// 如果有关键失败,提升消息级别
if (criticalFailures.Count > 0)
{
decisionCode = "RULE_CRITICAL_NG";
decisionMessage = $"第 {context.CurrentLayer} 层关键规则失败: {string.Join(", ", criticalFailures.Select(r => r.RuleName))}";
}
return new RuntimeDecisionDto
{
IsPass = isPass,
Code = decisionCode,
Message = decisionMessage
};
}
/// <summary>
/// 使用默认规则评估。
/// </summary>
private Result<RuntimeDecisionDto> EvaluateWithDefaultRule(int currentLayer, InferenceResultDto inference)
{
var isPass = inference.Label == "normal" || inference.Label == "OK" || inference.Confidence < _options.NgConfidenceThreshold;
var decision = new RuntimeDecisionDto
{
IsPass = isPass,
Code = isPass ? "DEFAULT_RULE_PASS" : "DEFAULT_RULE_NG",
Message = isPass
? $"第 {currentLayer} 层默认规则通过。"
: $"第 {currentLayer} 层默认规则 NG{inference.Label} (置信度: {inference.Confidence:F3})"
};
return Result<RuntimeDecisionDto>.Success(decision, message: "默认规则判定完成");
}
/// <summary>
/// 释放资源。
/// </summary>
public void Dispose()
{
try
{
_rules.Clear();
_isInitialized = false;
_logger.LogInformation("规则引擎服务已释放");
}
catch (Exception ex)
{
_logger.LogError(ex, "释放规则引擎服务资源时发生错误");
}
}
}
#region
/// <summary>
/// 规则定义。
/// </summary>
internal sealed class RuleDefinition
{
public string Name { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
public int Layer { get; init; }
public List<RuleCondition> Conditions { get; init; } = new();
public RuleAction Action { get; init; } = new();
public bool IsCritical { get; init; }
public int Priority { get; init; }
public bool Enabled { get; init; }
public ConditionLogic ConditionLogic { get; init; } = ConditionLogic.And;
}
/// <summary>
/// 规则条件。
/// </summary>
internal sealed class RuleCondition
{
public ConditionType Type { get; init; }
public string Parameter { get; init; } = string.Empty;
public string Operator { get; init; } = string.Empty;
public string ExpectedValue { get; init; } = string.Empty;
}
/// <summary>
/// 规则动作。
/// </summary>
internal sealed class RuleAction
{
public ActionType Type { get; init; }
public Dictionary<string, object> Parameters { get; init; } = new();
}
/// <summary>
/// 规则评估上下文。
/// </summary>
internal sealed class RuleEvaluationContext
{
public int CurrentLayer { get; init; }
public InferenceResultDto Inference { get; init; } = new();
public DateTime Timestamp { get; init; }
}
/// <summary>
/// 规则评估结果。
/// </summary>
internal sealed class RuleEvaluationResult
{
public string RuleName { get; init; } = string.Empty;
public bool IsPass { get; init; }
public string Message { get; init; } = string.Empty;
public RuleAction? Action { get; init; }
public DateTime ExecutionTime { get; init; }
}
/// <summary>
/// 条件类型。
/// </summary>
internal enum ConditionType
{
LabelEquals,
ConfidenceThreshold,
QuantityCheck,
PositionCheck,
PlacementCheck,
ForbiddenCheck,
SequenceCheck
}
/// <summary>
/// 动作类型。
/// </summary>
internal enum ActionType
{
MarkAsOK,
MarkAsNG,
LogWarning,
Custom
}
/// <summary>
/// 条件逻辑。
/// </summary>
internal enum ConditionLogic
{
And,
Or
}
#endregion