GlobalVariableManager.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. using Newtonsoft.Json;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Collections.ObjectModel;
  5. using System.ComponentModel;
  6. using System.IO;
  7. using System.Linq;
  8. using TeamAAS.Communication.Config;
  9. using TeamAAS.Communication.Models;
  10. namespace TeamAAS.Communication
  11. {
  12. /// <summary>
  13. /// 全局变量管理器。懒汉单例:<c>GlobalVariableManager.Instance</c>。
  14. /// 变量分两级作用域:Global(机器级固定,不随产品变) / Product(随产品变)。
  15. /// 内存里是单一 <see cref="Variables"/> 列表(公式绑定层直接读它,零改动),
  16. /// Scope 只决定持久化去向与编辑入口:
  17. /// Global 子集存 Config\global_vars.json;Product 子集存 Products\&lt;名&gt;\global_vars.json。
  18. /// </summary>
  19. public class GlobalVariableManager
  20. {
  21. #region 单例
  22. private static readonly Lazy<GlobalVariableManager> _instance =
  23. new Lazy<GlobalVariableManager>(() => new GlobalVariableManager(), isThreadSafe: true);
  24. /// <summary>懒汉单例入口。首次访问时初始化,线程安全。</summary>
  25. public static GlobalVariableManager Instance => _instance.Value;
  26. #endregion
  27. private GlobalVariableManager()
  28. {
  29. Variables.CollectionChanged += (s, e) =>
  30. {
  31. if (e.NewItems != null)
  32. foreach (GlobalVariableModel v in e.NewItems)
  33. v.PropertyChanged += OnVariablePropertyChanged;
  34. if (e.OldItems != null)
  35. foreach (GlobalVariableModel v in e.OldItems)
  36. v.PropertyChanged -= OnVariablePropertyChanged;
  37. if (e.Action != System.Collections.Specialized.NotifyCollectionChangedAction.Replace)
  38. VariablesChanged?.Invoke();
  39. };
  40. }
  41. private void OnVariablePropertyChanged(object sender, PropertyChangedEventArgs e)
  42. {
  43. VariablesChanged?.Invoke();
  44. }
  45. /// <summary>全部全局变量(Global + 当前产品的 Product)。公式/结果注册表直接读它。</summary>
  46. public ObservableCollection<GlobalVariableModel> Variables { get; }
  47. = new ObservableCollection<GlobalVariableModel>();
  48. public event Action VariablesChanged;
  49. #region 变量 CRUD
  50. /// <summary>新建变量并加入 live 列表(默认 Product 作用域)。</summary>
  51. public void AddVariable(string dataType, VarScope scope = VarScope.Product)
  52. {
  53. Variables.Add(GlobalVariableModel.Create(dataType, scope, Variables.Count));
  54. }
  55. /// <summary>
  56. /// 按名称写入变量值,供插件/通讯/流程写入任意类型(object 变量可承载任何对象)。
  57. /// 变量不存在时返回 false。写入后自动触发 VariablesChanged → ResultRegistry 刷新公式数据。
  58. /// </summary>
  59. public bool SetValue(string varName, object value)
  60. {
  61. if (string.IsNullOrWhiteSpace(varName)) return false;
  62. var v = Variables.FirstOrDefault(x => x.Name == varName);
  63. if (v == null) return false;
  64. v.Value = value;
  65. return true;
  66. }
  67. public object GetValue(string varName)
  68. {
  69. var v = Variables.FirstOrDefault(x => x.Name == varName);
  70. return v?.Value;
  71. }
  72. public void DeleteVariable(GlobalVariableModel variable)
  73. {
  74. if (variable == null) return;
  75. int idx = Variables.IndexOf(variable);
  76. if (idx < 0) return;
  77. Variables.RemoveAt(idx);
  78. Reindex();
  79. }
  80. public void MoveUp(GlobalVariableModel variable)
  81. {
  82. if (variable == null) return;
  83. int idx = Variables.IndexOf(variable);
  84. if (idx <= 0) return;
  85. Variables.Move(idx, idx - 1);
  86. Reindex();
  87. }
  88. public void MoveDown(GlobalVariableModel variable)
  89. {
  90. if (variable == null) return;
  91. int idx = Variables.IndexOf(variable);
  92. if (idx >= Variables.Count - 1) return;
  93. Variables.Move(idx, idx + 1);
  94. Reindex();
  95. }
  96. private void Reindex()
  97. {
  98. for (int i = 0; i < Variables.Count; i++)
  99. Variables[i].Index = i;
  100. }
  101. public Dictionary<string, object> GetAllValuesDict()
  102. {
  103. var dict = new Dictionary<string, object>();
  104. foreach (var v in Variables)
  105. {
  106. if (!string.IsNullOrWhiteSpace(v.Name))
  107. dict[v.Name] = v.Value;
  108. }
  109. return dict;
  110. }
  111. public Dictionary<string, Dictionary<string, object>> GetAsResultRegistryFormat()
  112. {
  113. var nodeDict = new Dictionary<string, object>();
  114. foreach (var v in Variables)
  115. {
  116. if (!string.IsNullOrWhiteSpace(v.Name))
  117. nodeDict[v.Name] = v.Value;
  118. }
  119. return new Dictionary<string, Dictionary<string, object>>
  120. {
  121. ["GlobalVariables"] = nodeDict
  122. };
  123. }
  124. #endregion
  125. #region 作用域克隆 / 提交(供编辑器 克隆-编辑-提交,取消可回滚)
  126. /// <summary>返回指定作用域变量的深拷贝列表(编辑器工作集,改动不影响 live)。</summary>
  127. public List<GlobalVariableModel> CloneScoped(VarScope scope)
  128. => Variables.Where(v => v.Scope == scope).Select(v => v.Clone()).ToList();
  129. /// <summary>
  130. /// 用给定项替换 live <see cref="Variables"/> 中该作用域的全部变量(另一作用域保持不动)。
  131. /// 会触发 CollectionChanged → VariablesChanged,ResultRegistry 已订阅并自动刷新公式数据。
  132. /// 必须在 UI 线程调用(Variables 绑定 UI)。
  133. /// </summary>
  134. public void ReplaceScoped(VarScope scope, IEnumerable<GlobalVariableModel> items)
  135. {
  136. for (int i = Variables.Count - 1; i >= 0; i--)
  137. if (Variables[i].Scope == scope) Variables.RemoveAt(i);
  138. if (items != null)
  139. foreach (var v in items)
  140. {
  141. v.Scope = scope; // 统一盖章,防外部传入 Scope 不一致
  142. Variables.Add(v);
  143. }
  144. Reindex();
  145. }
  146. #endregion
  147. #region 持久化:Global 机器级 / Product 产品级子集
  148. /// <summary>机器级 Global 变量文件:Config\global_vars.json。</summary>
  149. public static string GlobalFixedVarsFile => TeamAAS.PathHelper.GlobalFixedVarsFile;
  150. /// <summary>启动时载入机器级 Global 变量(替换 live 列表中的 Global 子集)。</summary>
  151. public void LoadGlobalScoped()
  152. {
  153. try
  154. {
  155. var list = new List<GlobalVariableModel>();
  156. var path = GlobalFixedVarsFile;
  157. if (File.Exists(path))
  158. {
  159. var json = File.ReadAllText(path);
  160. list = JsonConvert.DeserializeObject<List<GlobalVariableModel>>(json) ?? new List<GlobalVariableModel>();
  161. }
  162. foreach (var v in list) v.Scope = VarScope.Global;
  163. ReplaceScoped(VarScope.Global, list);
  164. }
  165. catch { }
  166. }
  167. /// <summary>把 live 列表中 Global 子集写入机器级文件。</summary>
  168. public void SaveGlobalScoped()
  169. {
  170. try
  171. {
  172. var path = GlobalFixedVarsFile;
  173. var dir = Path.GetDirectoryName(path);
  174. if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
  175. var list = Variables.Where(v => v.Scope == VarScope.Global).ToList();
  176. File.WriteAllText(path, JsonConvert.SerializeObject(list, Formatting.Indented));
  177. }
  178. catch { }
  179. }
  180. /// <summary>把 live 列表中 Product 子集序列化到指定产品快照文件。</summary>
  181. public void SaveSnapshot(string filePath)
  182. {
  183. try
  184. {
  185. var dir = Path.GetDirectoryName(filePath);
  186. if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
  187. Directory.CreateDirectory(dir);
  188. var snap = new GlobalVarSnapshot
  189. {
  190. Variables = Variables.Where(v => v.Scope == VarScope.Product).ToList()
  191. };
  192. File.WriteAllText(filePath, JsonConvert.SerializeObject(snap, Formatting.Indented));
  193. }
  194. catch { }
  195. }
  196. /// <summary>
  197. /// 从产品快照恢复 Product 子集(Global 子集保持不动)。
  198. /// 文件不存在(老产品没存过)时清空 Product 子集——切到新产品应从空开始,而非继承上个产品的变量。
  199. /// 必须在 UI 线程调用。
  200. /// </summary>
  201. public bool LoadSnapshot(string filePath)
  202. {
  203. try
  204. {
  205. var list = new List<GlobalVariableModel>();
  206. if (File.Exists(filePath))
  207. {
  208. var json = File.ReadAllText(filePath);
  209. var snap = JsonConvert.DeserializeObject<GlobalVarSnapshot>(json);
  210. list = snap?.Variables ?? new List<GlobalVariableModel>();
  211. }
  212. foreach (var v in list) v.Scope = VarScope.Product;
  213. ReplaceScoped(VarScope.Product, list);
  214. return File.Exists(filePath);
  215. }
  216. catch { return false; }
  217. }
  218. #endregion
  219. /// <summary>读取某产品快照文件的 Product 变量(不改 live 列表);文件不存在返回空列表。</summary>
  220. public List<GlobalVariableModel> ReadSnapshotList(string filePath)
  221. {
  222. try
  223. {
  224. if (!File.Exists(filePath)) return new List<GlobalVariableModel>();
  225. var snap = JsonConvert.DeserializeObject<GlobalVarSnapshot>(File.ReadAllText(filePath));
  226. var list = snap?.Variables ?? new List<GlobalVariableModel>();
  227. foreach (var v in list) v.Scope = VarScope.Product;
  228. return list;
  229. }
  230. catch { return new List<GlobalVariableModel>(); }
  231. }
  232. /// <summary>把给定 Product 变量写入某产品快照文件(不改 live 列表)。</summary>
  233. public void WriteSnapshotList(string filePath, IEnumerable<GlobalVariableModel> items)
  234. {
  235. try
  236. {
  237. var dir = Path.GetDirectoryName(filePath);
  238. if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir);
  239. var list = (items ?? Enumerable.Empty<GlobalVariableModel>()).ToList();
  240. foreach (var v in list) v.Scope = VarScope.Product;
  241. var snap = new GlobalVarSnapshot { Variables = list };
  242. File.WriteAllText(filePath, JsonConvert.SerializeObject(snap, Formatting.Indented));
  243. }
  244. catch { }
  245. }
  246. /// <summary>加载全局事件配置(心跳 / 产品切换事件)。静态,与变量作用域无关。</summary>
  247. public static GlobalEventConfig LoadGlobalEventConfig()
  248. {
  249. try
  250. {
  251. var path = TeamAAS.PathHelper.GlobalEventConfigFile;
  252. if (!File.Exists(path)) return new GlobalEventConfig();
  253. var json = File.ReadAllText(path);
  254. return JsonConvert.DeserializeObject<GlobalEventConfig>(json) ?? new GlobalEventConfig();
  255. }
  256. catch { return new GlobalEventConfig(); }
  257. }
  258. }
  259. }