Config.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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<T>(string key, T? value, bool encrypted);
  10. /// <summary>
  11. /// 将指定数据从缓存中移除。
  12. /// <para/> 这不会写入数据,还需要调用 <see cref="Save"/> 以保存。
  13. /// </summary>
  14. void RemoveFromCache(string key);
  15. T? Read<T>(string key, T? defaultValue, bool encrypted);
  16. bool HasValue(string key);
  17. void DiscardCache();
  18. void Save();
  19. }
  20. public class JsonConfigProvider : IConfigProvider {
  21. private readonly string filePath;
  22. public JsonConfigProvider(string filePath) {
  23. this.filePath = filePath;
  24. InitJson();
  25. }
  26. // 原始文件缓存
  27. private Lazy<JObject> json;
  28. // 反序列化后的结果缓存
  29. private readonly ConcurrentDictionary<string, object?> cache = new();
  30. private void InitJson() {
  31. json = new(() => {
  32. try {
  33. return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : [];
  34. } catch (Exception ex) {
  35. Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Alert);
  36. return [];
  37. }
  38. });
  39. }
  40. public void SetToCache<T>(string key, T? value, bool encrypted) {
  41. cache[key] = value;
  42. if (value is null) {
  43. // 直接在 JSON 中表示为 null
  44. json.Value[key] = JValue.CreateNull();
  45. } else if (encrypted) {
  46. // 需要加密,在 JSON 中保存密文字符串
  47. json.Value[key] = CryptographyUtils.AesEncrypt(value is string str ? str : JsonConvert.SerializeObject(value)); // UNDONE: 加密改为识别码
  48. } else {
  49. // 用 JToken 保留原始结构
  50. json.Value[key] = JToken.FromObject(value);
  51. }
  52. }
  53. public T? Read<T>(string key, T? defaultValue, bool encrypted) {
  54. if (cache.TryGetValue(key, out var cachedValue)) return (T?) cachedValue; // 读取缓存
  55. if (!json.Value.TryGetValue(key, out var entry)) return defaultValue; // 未找到该键
  56. try {
  57. T? result;
  58. if (entry.Type is JTokenType.Null or JTokenType.Undefined) {
  59. result = default;
  60. } else if (encrypted) {
  61. string plainText = CryptographyUtils.AesDecrypt(entry.Value<string>())!;
  62. result = typeof(T) == typeof(string) ? (T) (object) plainText : JsonConvert.DeserializeObject<T>(plainText);
  63. } else {
  64. result = entry.ToObject<T>();
  65. }
  66. cache[key] = result;
  67. return result;
  68. } catch (CryptographicException ex) {
  69. Logger.Error(ex, $"解密配置失败,该配置将被重置({key})", LogBehavior.Alert);
  70. RemoveFromCache(key);
  71. Save();
  72. return defaultValue;
  73. }
  74. }
  75. public bool HasValue(string key)
  76. => json.Value.ContainsKey(key);
  77. public void RemoveFromCache(string key) {
  78. json.Value.Remove(key);
  79. cache.TryRemove(key, out _);
  80. }
  81. public void DiscardCache() {
  82. InitJson();
  83. cache.Clear();
  84. }
  85. public void Save()
  86. => FileUtils.Write(filePath, json.Value.ToString(Formatting.Indented));
  87. }
  88. public static class ConfigUtils {
  89. public static readonly JsonConfigProvider AppData = new(Path.Combine(Paths.AppDataThenName, "config.json"));
  90. /// <summary>
  91. /// 从 secret.json 中读取特定密钥,如果未找到对应密钥则抛出异常。
  92. /// </summary>
  93. public static string GetSecret(string key)
  94. => secret.Read<string>(key, null, false) ?? throw new KeyNotFoundException($"Secret not found: {key}");
  95. private static readonly JsonConfigProvider secret = new(Path.Combine(Paths.AppData, "secret.json"));
  96. }
  97. public class ConfigEntry<T>(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) {
  98. /// <summary>
  99. /// 当设置项的值实际被改变时触发,参数为新值。
  100. /// </summary>
  101. public event Action<T?, IConfigProvider>? Changed;
  102. public IConfigProvider DefaultProvider => defaultProvider ?? ConfigUtils.AppData;
  103. public void Set(T? value, IConfigProvider? providerOverride = null) {
  104. var provider = providerOverride ?? DefaultProvider;
  105. T? current = Get(provider);
  106. if ((current is null && value is null) || (current is not null && value is not null && current.Equals(value))) return; // 值未改变,无需更新
  107. provider.SetToCache(key, value, encrypted);
  108. provider.Save();
  109. Changed?.Invoke(value, provider);
  110. }
  111. public void Reset(IConfigProvider? providerOverride = null) {
  112. var provider = providerOverride ?? DefaultProvider;
  113. if (!provider.HasValue(key)) return; // 值未设置,无需重置
  114. provider.RemoveFromCache(key);
  115. provider.Save();
  116. Changed?.Invoke(defaultValue, provider);
  117. }
  118. public T? Get(IConfigProvider? providerOverride = null)
  119. => (providerOverride ?? DefaultProvider).Read(key, defaultValue, encrypted);
  120. public bool HasValue(IConfigProvider? providerOverride = null)
  121. => (providerOverride ?? DefaultProvider).HasValue(key);
  122. public void Save(IConfigProvider? providerOverride = null)
  123. => (providerOverride ?? DefaultProvider).Save();
  124. public static implicit operator T?(ConfigEntry<T> entry) => entry.Get();
  125. }