Config.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. using System.Collections.Concurrent;
  2. using System.Security.Cryptography;
  3. namespace MeloongCore;
  4. public interface IConfigProvider {
  5. /// <summary>
  6. /// 将数据写入缓存。
  7. /// <para/> 这不会写入数据,还需要调用 <see cref="Save"/> 以保存。
  8. /// </summary>
  9. void SetToCache(string key, string? value, bool encrypted = false);
  10. /// <summary>
  11. /// 将指定数据从缓存中移除。
  12. /// <para/> 这不会写入数据,还需要调用 <see cref="Save"/> 以保存。
  13. /// </summary>
  14. void RemoveFromCache(string key);
  15. string? Read(string key, string? defaultValue = null, bool encrypted = false);
  16. bool HasValue(string key);
  17. void ClearCache();
  18. void Save();
  19. }
  20. public class JsonConfigProvider : IConfigProvider {
  21. private readonly string filePath;
  22. private readonly ConcurrentDictionary<string, string?> decryptedContentCache = new();
  23. public JsonConfigProvider(string filePath) {
  24. this.filePath = filePath;
  25. ResetContent();
  26. }
  27. /// <summary>
  28. /// 读取到的原始内容。
  29. /// </summary>
  30. public Lazy<JObject> Content { get; private set; }
  31. private void ResetContent() {
  32. Content = new(() => {
  33. try {
  34. return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : [];
  35. } catch (Exception ex) {
  36. Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Toast);
  37. return [];
  38. }
  39. });
  40. }
  41. public void SetToCache(string key, string? value, bool encrypted = false) {
  42. if (encrypted) {
  43. Content.Value[key] = CryptographyUtils.AesEncrypt(value); // UNDONE: 改为识别码
  44. decryptedContentCache[key] = value;
  45. } else {
  46. Content.Value[key] = value;
  47. }
  48. }
  49. public string? Read(string key, string? defaultValue = null, bool encrypted = false) {
  50. if (!Content.Value.ContainsKey(key)) return defaultValue;
  51. if (encrypted && decryptedContentCache.TryGetValue(key, out var cachedValue)) return cachedValue; // 读取缓存中已经解密的值
  52. // 读取值
  53. string? value = Content.Value[key]?.ToString();
  54. if (!encrypted) return value;
  55. // 解密读取的数据
  56. try {
  57. return decryptedContentCache.GetOrAdd(key, _ => CryptographyUtils.AesDecrypt(value));
  58. } catch (CryptographicException ex) {
  59. Logger.Error(ex, $"解密配置失败({key})", LogBehavior.Toast);
  60. RemoveFromCache(key);
  61. Save();
  62. return defaultValue;
  63. }
  64. }
  65. public bool HasValue(string key)
  66. => Content.Value.ContainsKey(key);
  67. public void RemoveFromCache(string key) {
  68. Content.Value.Remove(key);
  69. decryptedContentCache.TryRemove(key, out _);
  70. }
  71. public void ClearCache() {
  72. ResetContent();
  73. decryptedContentCache.Clear();
  74. }
  75. public void Save()
  76. => FileUtils.Write(filePath, Content.Value.ToString(Formatting.Indented));
  77. }
  78. public static class Configs {
  79. public static readonly JsonConfigProvider AppData = new(Path.Combine(Paths.AppDataThenName, "config.json"));
  80. /// <summary>
  81. /// 从 secret.json 中读取特定密钥,如果未找到对应密钥则抛出异常。
  82. /// </summary>
  83. public static string GetSecret(string key) => secret.Read(key)?.ToString() ?? throw new KeyNotFoundException($"Secret not found: {key}");
  84. private static readonly JsonConfigProvider secret = new(Path.Combine(Paths.AppData, "secret.json"));
  85. }
  86. public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) where T : IEquatable<T> {
  87. private IConfigProvider GetProvider(IConfigProvider? provider)
  88. => provider ?? defaultProvider ?? Configs.AppData;
  89. /// <summary>
  90. /// 当设置项的值实际被改变时触发,参数为新值。
  91. /// </summary>
  92. public event Action<T?>? OnChanged;
  93. public T? Get(IConfigProvider? providerOverride = null) {
  94. var provider = GetProvider(providerOverride);
  95. if (!provider.HasValue(key)) return defaultValue;
  96. string? value = provider.Read(key, defaultValue?.ToString(), encrypted);
  97. try {
  98. if (value is null) return default;
  99. var type = typeof(T);
  100. if (type == typeof(string)) return (T) (object) value;
  101. if (typeof(JToken).IsAssignableFrom(type)) return (T) (object) value.DeserializeJson()!;
  102. return (T) Convert.ChangeType(value, type);
  103. } catch (Exception ex) {
  104. Logger.Error(ex, $"读取配置项失败({key})", LogBehavior.Toast);
  105. return defaultValue;
  106. }
  107. }
  108. public void Set(T? value, IConfigProvider? providerOverride = null) {
  109. var provider = GetProvider(providerOverride);
  110. T? current = Get(provider);
  111. if ((current is null && value is null) || (current is not null && value is not null && current.Equals(value))) return; // 值未改变,无需更新
  112. provider.SetToCache(key, value?.ToString(), encrypted);
  113. provider.Save();
  114. OnChanged?.Invoke(value);
  115. }
  116. public bool HasValue(IConfigProvider? providerOverride = null) {
  117. var provider = GetProvider(providerOverride);
  118. return provider.HasValue(key);
  119. }
  120. public void Reset(IConfigProvider? providerOverride = null) {
  121. var provider = GetProvider(providerOverride);
  122. if (!provider.HasValue(key)) return; // 值未设置,无需重置
  123. provider.RemoveFromCache(key);
  124. provider.Save();
  125. OnChanged?.Invoke(defaultValue);
  126. }
  127. public void Save(IConfigProvider? providerOverride = null) {
  128. var provider = GetProvider(providerOverride);
  129. provider.Save();
  130. }
  131. }