Config.cs 6.9 KB

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