using PropertyGridLib.Controls; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using TeamAAS.FlowEditor.Execution; using TeamAAS.FlowEditor.Models; using TeamAAS.FlowEditor.Plugins; using Plugins.Standard.Models; using Plugins.Standard.Views; namespace Plugins.Standard { [Serializable] /// /// 组合模块插件 - 包含子流程,支持循环执行 /// [Plugin("组合模块", PluginCategory.逻辑判断, typeof(GroupPluginModel), typeof(GroupEditorView), "FolderOutline",NodeShape = NodeCategory.Group, Description = "组合模块,包含子流程,支持循环执行")] public class GroupPlugin : BasePlugin { public override List DeclareOutputs() { var list = new List(); if (Model?.ModuleOutputs != null) { foreach (var kv in Model.ModuleOutputs) { if (string.IsNullOrWhiteSpace(kv.OutName)) continue; list.Add(new OutputField(kv.OutName, kv.ResultType)); } } list.Add(new OutputField("实际循环次数", typeof(int))); list.Add(new OutputField("循环设置", typeof(string))); list.Add(new OutputField("Message", typeof(string))); return list; } public override bool InitPlugin() { Model.EnsureSubGraph(); return true; } [Newtonsoft.Json.JsonIgnore] public int CurrentCount { get; set; } = 0; public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary results) { results = new Dictionary(); Model.EnsureSubGraph(); if (Model.LoopCount == 0) { Log(2, "循环次数为 0,跳过组合模块执行"); return NodeRunStatus.Skipped; } Log(3, $"组合模块开始,循环设置={(Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString())},输入 {Model.ModuleInputs?.Count ?? 0} 个、输出 {Model.ModuleOutputs?.Count ?? 0} 个"); Model.ModuleOutputs.ForEach(x => x.ResultList?.Clear()); Model.IsBreakRequested = false; int maxLoops = Model.LoopCount == -1 ? int.MaxValue : Model.LoopCount; int actualLoops = 0; for (int i = 0; i < maxLoops; i++) { if (token.IsCancellationRequested) { break; } CurrentCount = i + 1; Log(4, $"第 {i + 1} 次循环开始"); foreach (var node in Model.SubGraph.Nodes) { node.Status = NodeRunStatus.NotStarted; node.CostTime = 0; } // ResetAllNodes 会 ClearFlow 清掉子流程所有结果, // 所以输入必须在每次循环的 Reset 之后注册 RegisterInputs(); var executor = new FlowExecutor(Model.SubGraph) { SkipReset = true }; executor.EndNodeEncountered += () => { Model.IsBreakRequested = true; }; executor.ExecuteAsync(token).Wait(); actualLoops++; bool IsEnd = Model.IsBreakRequested || i == maxLoops - 1; SaveOutPut(ref results, IsEnd); if (Model.IsBreakRequested) { results["Message"] = $"第{i + 1}次循环时遇到结束节点"; Log(2, $"第 {i + 1} 次循环遇到结束节点,跳出循环"); break; } } results["实际循环次数"] = actualLoops; results["循环设置"] = Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString(); if (token.IsCancellationRequested) { Log(1, $"组合模块被取消,已执行 {actualLoops} 次循环", TeamAAS.LogLevel.Warning); return NodeRunStatus.Failed; } Log(2, $"组合模块执行完成,实际循环 {actualLoops} 次"); return NodeRunStatus.Success; } /// /// 将 ModuleInputs 映射到 ResultRegistry 子流程区域 /// 内部节点通过 &{输入.变量名} 引用 /// private void RegisterInputs() { if (Model?.ModuleInputs == null || Model.ModuleInputs.Count == 0) return; var subGraphId = Model.SubGraph.GraphId; var flowId = GetModel.FlowId; System.Diagnostics.Debug.WriteLine($"[GroupPlugin] RegisterInputs: flowId={flowId}, subGraphId={subGraphId}"); foreach (var input in Model.ModuleInputs) { if (string.IsNullOrWhiteSpace(input.InputName)) continue; if (input.SourceValue == null) continue; object value = null; if (!string.IsNullOrWhiteSpace(input.SourceValue.Formula)) { value = RegistryOrDebug.ResolveFormula(flowId, input.SourceValue.Formula); System.Diagnostics.Debug.WriteLine($"[GroupPlugin] {input.InputName} ← formula='{input.SourceValue.Formula}' → value={value} (null? {value == null})"); } else { System.Diagnostics.Debug.WriteLine($"[GroupPlugin] {input.InputName} ← 无公式"); } RegistryOrDebug.SetInputVariable(subGraphId, input.InputName, value); } RegistryOrDebug.GetFlowResults(subGraphId, out var after); System.Diagnostics.Debug.WriteLine($"[GroupPlugin] 验证子流程「输入」节点: " + (after.ContainsKey("输入") ? $"OK ({after["输入"].Count} vars)" : "MISSING!")); } public void SaveOutPut(ref Dictionary results, bool IsEnd) { foreach (var kv in Model.ModuleOutputs) { if (kv.ResultList.Count == 0) { kv.ResultType = this.GetValue(kv.OutVale).GetType(); } if (!kv.IsEndResult)//如果以列表形式输出,保存每次循环的结果 { kv.ResultList.Add(GetValue(kv.OutVale)); if (kv.ResultList.Count > kv.MaxResultNumber) { kv.ResultList = kv.ResultList.Skip(kv.ResultList.Count - kv.MaxResultNumber).ToList(); } } if (IsEnd) { if (kv.IsEndResult)//如果是结束节点,保存输出 { results.Add(kv.OutName, this.GetValue(kv.OutVale)); } else { if (kv.ResultList.Count > 0) { // 方法:使用 Enumerable.Cast 的反射调用 var castMethod = typeof(Enumerable) .GetMethod("Cast") .MakeGenericMethod(kv.ResultType); var casted = castMethod.Invoke(null, new object[] { kv.ResultList }); // 返回 IEnumerable // 然后调用 ToList(也可用反射或直接转为 IEnumerable) var toListMethod = typeof(Enumerable) .GetMethod("ToList") .MakeGenericMethod(kv.ResultType); var result = toListMethod.Invoke(null, new object[] { casted }); // 返回 List results.Add(kv.OutName, result); } else results.Add(kv.OutName, kv.ResultList); } } } } /// /// 通过公式获取节点执行结果值(优先从子流程节点本地读取) /// public new T GetValue(FormulaBound formula) { if (formula == null) return default(T); object _result = ResolveFromSubGraph(formula.Formula); if (_result == null || _result.ToString() == "") return formula.Value; if (_result is T typedResult) return typedResult; return (T)System.Convert.ChangeType(_result, typeof(T)); } /// /// 从子流程节点本地解析 &{节点名.属性名},跨流程 &{流程名.节点名.属性名} 回退到 ResultRegistry /// private object ResolveFromSubGraph(string formula) { if (string.IsNullOrEmpty(formula)) return formula; var match2 = System.Text.RegularExpressions.Regex.Match(formula, @"^&\{(?[^.}]+)\.(?[^.}]+)\}$"); if (match2.Success) { var nodeName = match2.Groups["node"].Value; var propName = match2.Groups["prop"].Value; var node = Model.SubGraph?.Nodes?.FirstOrDefault(n => n.NodeName == nodeName); if (node?.RawResults != null && node.RawResults.TryGetValue(propName, out var val)) return val; return null; } var match3 = System.Text.RegularExpressions.Regex.Match(formula, @"^&\{(?[^.}]+)\.(?[^.}]+)\.(?[^.}]+)\}$"); if (match3.Success) { return RegistryOrDebug.GetValue( match3.Groups["flow"].Value, match3.Groups["node"].Value, match3.Groups["prop"].Value); } return formula; } } }