Config.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. using System.Security.Cryptography;
  2. namespace MeloongCore;
  3. public interface IConfigProvider {
  4. void Set<T>(string key, T? value, bool encrypted);
  5. void Remove(string key);
  6. T? Read<T>(string key, T? defaultValue, bool encrypted);
  7. bool HasValue(string key);
  8. void DiscardCache();
  9. }
  10. public class JsonConfigProvider : IConfigProvider {
  11. public static readonly ConcurrentBag<WeakReference<JsonConfigProvider>> allProviders = [];
  12. private readonly string filePath;
  13. public JsonConfigProvider(string filePath) {
  14. this.filePath = filePath;
  15. allProviders.Add(new(this));
  16. saveAction = Throttler.Throttle(Save, TimeSpan.FromMilliseconds(500), leading: false, trailing: true);
  17. InitJson();
  18. }
  19. // 原始文件缓存
  20. private Lazy<JObject> json;
  21. // 反序列化后的结果缓存
  22. private readonly ConcurrentDictionary<string, object?> cache = new();
  23. private void InitJson() {
  24. json = new(() => {
  25. try {
  26. return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : [];
  27. } catch (Exception ex) {
  28. Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Alert);
  29. return [];
  30. }
  31. });
  32. }
  33. // ================================= 设置、读取、缓存 =================================
  34. public void Set<T>(string key, T? value, bool encrypted) {
  35. cache[key] = value;
  36. lock (this) {
  37. if (value is null) {
  38. // 直接在 JSON 中表示为 null
  39. json.Value[key] = JValue.CreateNull();
  40. Logger.Trace($"配置已修改:{key} = null({filePath})");
  41. } else if (encrypted) {
  42. // 需要加密,在 JSON 中保存密文字符串
  43. throw new NotImplementedException("加密需要改为使用识别码,现在尚未实现。");
  44. Logger.Trace($"配置已修改:{key} = 已加密({filePath})");
  45. json.Value[key] = CryptographyUtils.AesEncrypt(value is string str ? str : JsonConvert.SerializeObject(value));
  46. } else {
  47. // 用 JToken 保留原始结构
  48. JToken token = JToken.FromObject(value);
  49. json.Value[key] = token;
  50. Logger.Trace($"配置已修改:{key} = {token.ToString(Formatting.None)}({filePath})");
  51. }
  52. }
  53. MakeDirty();
  54. }
  55. public T? Read<T>(string key, T? defaultValue, bool encrypted) {
  56. if (cache.TryGetValue(key, out var cachedValue)) return (T?) cachedValue; // 读取缓存
  57. JToken entry;
  58. lock (this) {
  59. if (!json.Value.TryGetValue(key, out entry!)) return defaultValue; // 未找到该键
  60. entry = entry.DeepClone();
  61. }
  62. try {
  63. T? result;
  64. if (entry.Type is JTokenType.Null or JTokenType.Undefined) {
  65. result = default;
  66. } else if (encrypted) {
  67. string plainText = CryptographyUtils.AesDecrypt(entry.Value<string>())!;
  68. result = typeof(T) == typeof(string) ? (T) (object) plainText : JsonConvert.DeserializeObject<T>(plainText);
  69. } else {
  70. result = entry.ToObject<T>();
  71. }
  72. cache[key] = result;
  73. return result;
  74. } catch (CryptographicException ex) {
  75. Logger.Error(ex, $"解密配置失败,该配置将被重置({key})", LogBehavior.Alert);
  76. Remove(key);
  77. return defaultValue;
  78. }
  79. }
  80. public bool HasValue(string key) {
  81. lock (this) return json.Value.ContainsKey(key);
  82. }
  83. public void Remove(string key) {
  84. lock (this) json.Value.Remove(key);
  85. cache.TryRemove(key, out _);
  86. MakeDirty();
  87. }
  88. public void DiscardCache() {
  89. lock (this) InitJson();
  90. cache.Clear();
  91. }
  92. // ===================================== 保存 =====================================
  93. private readonly RateLimitedAction saveAction;
  94. private bool isDirty = false;
  95. public void MakeDirty() {
  96. isDirty = true;
  97. saveAction.Invoke();
  98. }
  99. public void Save() {
  100. if (!isDirty) return;
  101. isDirty = false;
  102. lock (this) FileUtils.Write(filePath, json.Value.ToString(Formatting.Indented));
  103. }
  104. }
  105. public static class ConfigUtils {
  106. public static readonly JsonConfigProvider AppData = new(Path.Combine(Paths.AppDataThenName, "config.json"));
  107. /// <summary>
  108. /// 立即保存所有配置。
  109. /// </summary>
  110. public static void SaveAll() {
  111. foreach (var weakRef in JsonConfigProvider.allProviders) {
  112. if (weakRef.TryGetTarget(out var provider)) provider.Save();
  113. }
  114. }
  115. /// <summary>
  116. /// 从 secret.json 中读取特定密钥,如果未找到对应密钥则抛出异常。
  117. /// </summary>
  118. public static string GetSecret(string key)
  119. => secret.Read<string>(key, null, false) ?? throw new KeyNotFoundException($"未找到密钥:{key}");
  120. private static readonly JsonConfigProvider secret = new(Path.Combine(Paths.AppData, "secret.json"));
  121. }
  122. public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) {
  123. public IConfigProvider DefaultProvider => defaultProvider ?? ConfigUtils.AppData;
  124. /// <summary>
  125. /// 当设置项的值被改变时触发,参数为新值。新值可能和旧值相同。
  126. /// </summary>
  127. public event Action<T?, IConfigProvider>? Changed;
  128. public void Edit(Func<T?, T?> editFunc, IConfigProvider? providerOverride = null) {
  129. var provider = providerOverride ?? DefaultProvider;
  130. Set(editFunc(Get(provider)), provider);
  131. }
  132. public void Edit(RefAction editAction, IConfigProvider? providerOverride = null) {
  133. var provider = providerOverride ?? DefaultProvider;
  134. var current = Get(provider);
  135. editAction(ref current);
  136. Set(current, provider);
  137. }
  138. public delegate void RefAction(ref T? value);
  139. public void Set(T? value, IConfigProvider? providerOverride = null) {
  140. var provider = providerOverride ?? DefaultProvider;
  141. provider.Set(key, value, encrypted);
  142. Changed?.Invoke(value, provider);
  143. }
  144. public void Reset(IConfigProvider? providerOverride = null) {
  145. var provider = providerOverride ?? DefaultProvider;
  146. if (!provider.HasValue(key)) return; // 值未设置,无需重置
  147. provider.Remove(key);
  148. Changed?.Invoke(defaultValue, provider);
  149. }
  150. public T? Get(IConfigProvider? providerOverride = null)
  151. => (providerOverride ?? DefaultProvider).Read(key, defaultValue, encrypted);
  152. public bool HasValue(IConfigProvider? providerOverride = null)
  153. => (providerOverride ?? DefaultProvider).HasValue(key);
  154. public static implicit operator T?(ConfigEntry<T> entry) => entry.Get();
  155. }