Config.cs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. using System.Security.Cryptography;
  2. namespace MeloongCore;
  3. #region ConfigProvider | 配置提供器
  4. public interface IConfigProvider {
  5. void Set<T>(string key, T? value, bool encrypted);
  6. void Remove(string key);
  7. T? Read<T>(string key, T? defaultValue, bool encrypted);
  8. bool HasValue(string key);
  9. void DiscardCache();
  10. }
  11. public class JsonConfigProvider : IConfigProvider {
  12. public static readonly ConcurrentBag<WeakReference<JsonConfigProvider>> allProviders = [];
  13. private readonly string filePath;
  14. #pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
  15. public JsonConfigProvider(string filePath) {
  16. #pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑添加 'required' 修饰符或声明为可以为 null。
  17. this.filePath = filePath;
  18. allProviders.Add(new(this));
  19. saveWorker = new(Save, 500, RateLimitMode.WaitUnillNoNewInvoke);
  20. InitJson();
  21. }
  22. // 原始文件缓存
  23. private Lazy<JObject> json;
  24. // 反序列化后的结果缓存
  25. private readonly ConcurrentDictionary<string, object?> cache = new();
  26. private void InitJson() {
  27. json = new(() => {
  28. try {
  29. return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : [];
  30. } catch (Exception ex) {
  31. Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Alert);
  32. return [];
  33. }
  34. });
  35. }
  36. // ================================= 设置、读取、缓存 =================================
  37. public void Set<T>(string key, T? value, bool encrypted) {
  38. cache[key] = value;
  39. lock (this) {
  40. if (value is null) {
  41. // 直接在 JSON 中表示为 null
  42. json.Value[key] = JValue.CreateNull();
  43. Logger.Trace($"配置已修改:{key} = null({filePath})");
  44. } else if (encrypted) {
  45. // 需要加密,在 JSON 中保存密文字符串
  46. throw new NotImplementedException("加密需要改为使用识别码,现在尚未实现。");
  47. // Logger.Trace($"配置已修改:{key} = 已加密({filePath})");
  48. // json.Value[key] = CryptographyUtils.AesEncrypt(value is string str ? str : JsonConvert.SerializeObject(value));
  49. } else {
  50. // 用 JToken 保留原始结构
  51. JToken token = JToken.FromObject(value);
  52. json.Value[key] = token;
  53. Logger.Trace($"配置已修改:{key} = {token.ToString(Formatting.None)}({filePath})");
  54. }
  55. }
  56. MakeDirty();
  57. }
  58. public T? Read<T>(string key, T? defaultValue, bool encrypted) {
  59. if (cache.TryGetValue(key, out var cachedValue)) return (T?) cachedValue; // 读取缓存
  60. JToken entry;
  61. lock (this) {
  62. if (!json.Value.TryGetValue(key, out entry!)) return defaultValue; // 未找到该键
  63. entry = entry.DeepClone();
  64. }
  65. try {
  66. T? result;
  67. if (entry.Type is JTokenType.Null or JTokenType.Undefined) {
  68. result = default;
  69. } else if (encrypted) {
  70. string plainText = CryptographyUtils.AesDecrypt(entry.Value<string>())!;
  71. result = typeof(T) == typeof(string) ? (T) (object) plainText : JsonConvert.DeserializeObject<T>(plainText);
  72. } else {
  73. result = entry.ToObject<T>();
  74. }
  75. cache[key] = result;
  76. return result;
  77. } catch (CryptographicException ex) {
  78. Logger.Error(ex, $"解密配置失败,该配置将被重置({key})", LogBehavior.Alert);
  79. Remove(key);
  80. return defaultValue;
  81. }
  82. }
  83. public bool HasValue(string key) {
  84. lock (this) return json.Value.ContainsKey(key);
  85. }
  86. public void Remove(string key) {
  87. lock (this) json.Value.Remove(key);
  88. cache.TryRemove(key, out _);
  89. MakeDirty();
  90. }
  91. public void DiscardCache() {
  92. lock (this) InitJson();
  93. cache.Clear();
  94. }
  95. // ===================================== 保存 =====================================
  96. private readonly RateLimitedWorker saveWorker;
  97. private bool isDirty = false;
  98. public void MakeDirty() {
  99. isDirty = true;
  100. saveWorker.Start();
  101. }
  102. public void Save() {
  103. if (!isDirty) return;
  104. isDirty = false;
  105. lock (this) FileUtils.Write(filePath, json.Value.ToString(Formatting.Indented));
  106. }
  107. }
  108. public static class ConfigProviders {
  109. public static JsonConfigProvider Default = new(Path.Combine(Paths.AppDataThenName, "config.json"));
  110. public static readonly JsonConfigProvider Secret = new(Path.Combine(Paths.AppData, "secret.json"));
  111. }
  112. #endregion
  113. #region ConfigEntry | 单个配置项
  114. /// <summary>
  115. /// 当前获取到的配置值。
  116. /// </summary>
  117. public class PreviewGetEventArgs<T>(T? value) : EventArgs {
  118. public T? Value { get; set; } = value;
  119. }
  120. /// <summary>
  121. /// 使用固定的 <see cref="IConfigProvider"/> 的配置项。
  122. /// <para/>若不指定 <paramref name="provider"/>,则使用 <see cref="ConfigProviders.Default"/>。
  123. /// </summary>
  124. public class ConfigEntry<T>(string key, T? defaultValue, bool encrypted = false, IConfigProvider? provider = null) {
  125. public IConfigProvider Provider { get; } = provider ?? ConfigProviders.Default;
  126. /// <summary>
  127. /// 当调整配置项的值时触发,参数为新值。<para/>新值可能和旧值相同。
  128. /// </summary>
  129. public event Action<T?>? Changed;
  130. /// <summary>
  131. /// 当获取配置项的值时触发。
  132. /// </summary>
  133. public event Action<PreviewGetEventArgs<T>, IConfigProvider>? PreviewGet;
  134. public void Edit(Func<T?, T?> editFunc) => Set(editFunc(Get()));
  135. public void Edit(RefAction editAction) {
  136. var current = Get();
  137. editAction(ref current);
  138. Set(current);
  139. }
  140. public delegate void RefAction(ref T? value);
  141. public void Set(T? value) {
  142. Provider.Set(key, value, encrypted);
  143. Changed?.Invoke(value);
  144. }
  145. public void Reset() {
  146. if (!Provider.HasValue(key)) return; // 值未设置,无需重置
  147. Provider.Remove(key);
  148. Changed?.Invoke(defaultValue);
  149. }
  150. public T? Get() {
  151. PreviewGetEventArgs<T> args = new(Provider.Read(key, defaultValue, encrypted));
  152. PreviewGet?.Invoke(args, Provider);
  153. return args.Value;
  154. }
  155. public bool HasValue() => Provider.HasValue(key);
  156. }
  157. /// <summary>
  158. /// 在每次操作时动态指定 <see cref="IConfigProvider"/> 的配置项。
  159. /// </summary>
  160. public class DynamicConfigEntry<T>(string key, T? defaultValue, bool encrypted = false) {
  161. /// <summary>
  162. /// 当调整配置项的值时触发,参数为新值。<para/>新值可能和旧值相同。
  163. /// </summary>
  164. public event Action<T?>? Changed;
  165. /// <summary>
  166. /// 当获取配置项的值时触发。
  167. /// </summary>
  168. public event Action<PreviewGetEventArgs<T>, IConfigProvider>? PreviewGet;
  169. public void Edit(Func<T?, T?> editFunc, IConfigProvider provider) => Set(editFunc(Get(provider)), provider);
  170. public void Edit(RefAction editAction, IConfigProvider provider) {
  171. var current = Get(provider);
  172. editAction(ref current);
  173. Set(current, provider);
  174. }
  175. public delegate void RefAction(ref T? value);
  176. public void Set(T? value, IConfigProvider provider) {
  177. provider.Set(key, value, encrypted);
  178. Changed?.Invoke(value);
  179. }
  180. public void Reset(IConfigProvider provider) {
  181. if (!provider.HasValue(key)) return; // 值未设置,无需重置
  182. provider.Remove(key);
  183. Changed?.Invoke(defaultValue);
  184. }
  185. public T? Get(IConfigProvider provider) {
  186. PreviewGetEventArgs<T> args = new(provider.Read(key, defaultValue, encrypted));
  187. PreviewGet?.Invoke(args, provider);
  188. return args.Value;
  189. }
  190. public bool HasValue(IConfigProvider provider) => provider.HasValue(key);
  191. }
  192. #endregion
  193. public static class ConfigUtils {
  194. /// <summary>立即保存所有配置。</summary>
  195. public static void SaveAll() {
  196. foreach (var weakRef in JsonConfigProvider.allProviders) {
  197. if (weakRef.TryGetTarget(out var provider)) provider.Save();
  198. }
  199. }
  200. /// <summary>从 secret.json 中读取特定密钥,如果未找到对应密钥则抛出异常。</summary>
  201. public static string GetSecret(string key)
  202. => ConfigProviders.Secret.Read<string>(key, null, false) ?? throw new KeyNotFoundException($"未找到密钥:{key}");
  203. }