GroupPlugin.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. using PropertyGridLib.Controls;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Threading;
  6. using TeamAAS.FlowEditor.Execution;
  7. using TeamAAS.FlowEditor.Models;
  8. using TeamAAS.FlowEditor.Plugins;
  9. using Plugins.Standard.Models;
  10. using Plugins.Standard.Views;
  11. namespace Plugins.Standard
  12. {
  13. [Serializable]
  14. /// <summary>
  15. /// 组合模块插件 - 包含子流程,支持循环执行
  16. /// </summary>
  17. [Plugin("组合模块", PluginCategory.逻辑判断, typeof(GroupPluginModel), typeof(GroupEditorView),
  18. "FolderOutline",NodeShape = NodeCategory.Group, Description = "组合模块,包含子流程,支持循环执行")]
  19. public class GroupPlugin : BasePlugin<GroupPluginModel>
  20. {
  21. public override List<OutputField> DeclareOutputs()
  22. {
  23. var list = new List<OutputField>();
  24. if (Model?.ModuleOutputs != null)
  25. {
  26. foreach (var kv in Model.ModuleOutputs)
  27. {
  28. if (string.IsNullOrWhiteSpace(kv.OutName)) continue;
  29. list.Add(new OutputField(kv.OutName, kv.ResultType));
  30. }
  31. }
  32. list.Add(new OutputField("实际循环次数", typeof(int)));
  33. list.Add(new OutputField("循环设置", typeof(string)));
  34. list.Add(new OutputField("Message", typeof(string)));
  35. return list;
  36. }
  37. public override bool InitPlugin()
  38. {
  39. Model.EnsureSubGraph();
  40. return true;
  41. }
  42. [Newtonsoft.Json.JsonIgnore]
  43. public int CurrentCount { get; set; } = 0;
  44. public override NodeRunStatus PluginRun(CancellationToken token, out Dictionary<string, object> results)
  45. {
  46. results = new Dictionary<string, object>();
  47. Model.EnsureSubGraph();
  48. if (Model.LoopCount == 0)
  49. {
  50. Log(2, "循环次数为 0,跳过组合模块执行");
  51. return NodeRunStatus.Skipped;
  52. }
  53. Log(3, $"组合模块开始,循环设置={(Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString())},输入 {Model.ModuleInputs?.Count ?? 0} 个、输出 {Model.ModuleOutputs?.Count ?? 0} 个");
  54. Model.ModuleOutputs.ForEach(x => x.ResultList?.Clear());
  55. Model.IsBreakRequested = false;
  56. int maxLoops = Model.LoopCount == -1 ? int.MaxValue : Model.LoopCount;
  57. int actualLoops = 0;
  58. for (int i = 0; i < maxLoops; i++)
  59. {
  60. if (token.IsCancellationRequested)
  61. {
  62. break;
  63. }
  64. CurrentCount = i + 1;
  65. Log(4, $"第 {i + 1} 次循环开始");
  66. foreach (var node in Model.SubGraph.Nodes)
  67. {
  68. node.Status = NodeRunStatus.NotStarted;
  69. node.CostTime = 0;
  70. }
  71. // ResetAllNodes 会 ClearFlow 清掉子流程所有结果,
  72. // 所以输入必须在每次循环的 Reset 之后注册
  73. RegisterInputs();
  74. var executor = new FlowExecutor(Model.SubGraph) { SkipReset = true };
  75. executor.EndNodeEncountered += () =>
  76. {
  77. Model.IsBreakRequested = true;
  78. };
  79. executor.ExecuteAsync(token).Wait();
  80. actualLoops++;
  81. bool IsEnd = Model.IsBreakRequested || i == maxLoops - 1;
  82. SaveOutPut(ref results, IsEnd);
  83. if (Model.IsBreakRequested)
  84. {
  85. results["Message"] = $"第{i + 1}次循环时遇到结束节点";
  86. Log(2, $"第 {i + 1} 次循环遇到结束节点,跳出循环");
  87. break;
  88. }
  89. }
  90. results["实际循环次数"] = actualLoops;
  91. results["循环设置"] = Model.LoopCount == -1 ? "无限" : Model.LoopCount.ToString();
  92. if (token.IsCancellationRequested)
  93. {
  94. Log(1, $"组合模块被取消,已执行 {actualLoops} 次循环", TeamAAS.LogLevel.Warning);
  95. return NodeRunStatus.Failed;
  96. }
  97. Log(2, $"组合模块执行完成,实际循环 {actualLoops} 次");
  98. return NodeRunStatus.Success;
  99. }
  100. /// <summary>
  101. /// 将 ModuleInputs 映射到 ResultRegistry 子流程区域
  102. /// 内部节点通过 &amp;{输入.变量名} 引用
  103. /// </summary>
  104. private void RegisterInputs()
  105. {
  106. if (Model?.ModuleInputs == null || Model.ModuleInputs.Count == 0) return;
  107. var subGraphId = Model.SubGraph.GraphId;
  108. var flowId = GetModel.FlowId;
  109. System.Diagnostics.Debug.WriteLine($"[GroupPlugin] RegisterInputs: flowId={flowId}, subGraphId={subGraphId}");
  110. foreach (var input in Model.ModuleInputs)
  111. {
  112. if (string.IsNullOrWhiteSpace(input.InputName)) continue;
  113. if (input.SourceValue == null) continue;
  114. object value = null;
  115. if (!string.IsNullOrWhiteSpace(input.SourceValue.Formula))
  116. {
  117. value = RegistryOrDebug.ResolveFormula(flowId, input.SourceValue.Formula);
  118. System.Diagnostics.Debug.WriteLine($"[GroupPlugin] {input.InputName} ← formula='{input.SourceValue.Formula}' → value={value} (null? {value == null})");
  119. }
  120. else
  121. {
  122. System.Diagnostics.Debug.WriteLine($"[GroupPlugin] {input.InputName} ← 无公式");
  123. }
  124. RegistryOrDebug.SetInputVariable(subGraphId, input.InputName, value);
  125. }
  126. RegistryOrDebug.GetFlowResults(subGraphId, out var after);
  127. System.Diagnostics.Debug.WriteLine($"[GroupPlugin] 验证子流程「输入」节点: " + (after.ContainsKey("输入") ? $"OK ({after["输入"].Count} vars)" : "MISSING!"));
  128. }
  129. public void SaveOutPut(ref Dictionary<string, object> results, bool IsEnd)
  130. {
  131. foreach (var kv in Model.ModuleOutputs)
  132. {
  133. if (kv.ResultList.Count == 0)
  134. {
  135. kv.ResultType = this.GetValue(kv.OutVale).GetType();
  136. }
  137. if (!kv.IsEndResult)//如果以列表形式输出,保存每次循环的结果
  138. {
  139. kv.ResultList.Add(GetValue(kv.OutVale));
  140. if (kv.ResultList.Count > kv.MaxResultNumber)
  141. {
  142. kv.ResultList = kv.ResultList.Skip(kv.ResultList.Count - kv.MaxResultNumber).ToList();
  143. }
  144. }
  145. if (IsEnd)
  146. {
  147. if (kv.IsEndResult)//如果是结束节点,保存输出
  148. {
  149. results.Add(kv.OutName, this.GetValue(kv.OutVale));
  150. }
  151. else
  152. {
  153. if (kv.ResultList.Count > 0)
  154. {
  155. // 方法:使用 Enumerable.Cast 的反射调用
  156. var castMethod = typeof(Enumerable)
  157. .GetMethod("Cast")
  158. .MakeGenericMethod(kv.ResultType);
  159. var casted = castMethod.Invoke(null, new object[] { kv.ResultList }); // 返回 IEnumerable<int>
  160. // 然后调用 ToList(也可用反射或直接转为 IEnumerable)
  161. var toListMethod = typeof(Enumerable)
  162. .GetMethod("ToList")
  163. .MakeGenericMethod(kv.ResultType);
  164. var result = toListMethod.Invoke(null, new object[] { casted }); // 返回 List<int>
  165. results.Add(kv.OutName, result);
  166. }
  167. else
  168. results.Add(kv.OutName, kv.ResultList);
  169. }
  170. }
  171. }
  172. }
  173. /// <summary>
  174. /// 通过公式获取节点执行结果值(优先从子流程节点本地读取)
  175. /// </summary>
  176. public new T GetValue<T>(FormulaBound<T> formula)
  177. {
  178. if (formula == null)
  179. return default(T);
  180. object _result = ResolveFromSubGraph(formula.Formula);
  181. if (_result == null || _result.ToString() == "")
  182. return formula.Value;
  183. if (_result is T typedResult)
  184. return typedResult;
  185. return (T)System.Convert.ChangeType(_result, typeof(T));
  186. }
  187. /// <summary>
  188. /// 从子流程节点本地解析 &{节点名.属性名},跨流程 &{流程名.节点名.属性名} 回退到 ResultRegistry
  189. /// </summary>
  190. private object ResolveFromSubGraph(string formula)
  191. {
  192. if (string.IsNullOrEmpty(formula)) return formula;
  193. var match2 = System.Text.RegularExpressions.Regex.Match(formula, @"^&\{(?<node>[^.}]+)\.(?<prop>[^.}]+)\}$");
  194. if (match2.Success)
  195. {
  196. var nodeName = match2.Groups["node"].Value;
  197. var propName = match2.Groups["prop"].Value;
  198. var node = Model.SubGraph?.Nodes?.FirstOrDefault(n => n.NodeName == nodeName);
  199. if (node?.RawResults != null && node.RawResults.TryGetValue(propName, out var val))
  200. return val;
  201. return null;
  202. }
  203. var match3 = System.Text.RegularExpressions.Regex.Match(formula, @"^&\{(?<flow>[^.}]+)\.(?<node>[^.}]+)\.(?<prop>[^.}]+)\}$");
  204. if (match3.Success)
  205. {
  206. return RegistryOrDebug.GetValue(
  207. match3.Groups["flow"].Value,
  208. match3.Groups["node"].Value,
  209. match3.Groups["prop"].Value);
  210. }
  211. return formula;
  212. }
  213. }
  214. }