using System.Collections.Concurrent; using System.Security.Cryptography; namespace MeloongCore; public interface IConfigProvider { /// /// 将数据写入缓存。 /// 这不会写入数据,还需要调用 以保存。 /// void SetToCache(string key, string? value, bool encrypted = false); /// /// 将指定数据从缓存中移除。 /// 这不会写入数据,还需要调用 以保存。 /// void RemoveFromCache(string key); string? Read(string key, string? defaultValue = null, bool encrypted = false); bool HasValue(string key); void ClearCache(); void Save(); } public class JsonConfigProvider : IConfigProvider { private readonly string filePath; private readonly ConcurrentDictionary decryptedContentCache = new(); public JsonConfigProvider(string filePath) { this.filePath = filePath; ResetContent(); } /// /// 读取到的原始内容。 /// public Lazy Content { get; private set; } private void ResetContent() { Content = new(() => { try { return FileUtils.Exists(filePath) ? ((JObject?) FileUtils.ReadAsJson(filePath) ?? []) : []; } catch (Exception ex) { Logger.Error(ex, $"读取配置文件失败({filePath})", LogBehavior.Toast); return []; } }); } public void SetToCache(string key, string? value, bool encrypted = false) { if (encrypted) { Content.Value[key] = CryptographyUtils.AesEncrypt(value); // UNDONE: 改为识别码 decryptedContentCache[key] = value; } else { Content.Value[key] = value; } } public string? Read(string key, string? defaultValue = null, bool encrypted = false) { if (!Content.Value.ContainsKey(key)) return defaultValue; if (encrypted && decryptedContentCache.TryGetValue(key, out var cachedValue)) return cachedValue; // 读取缓存中已经解密的值 // 读取值 string? value = Content.Value[key]?.ToString(); if (!encrypted) return value; // 解密读取的数据 try { return decryptedContentCache.GetOrAdd(key, _ => CryptographyUtils.AesDecrypt(value)); } catch (CryptographicException ex) { Logger.Error(ex, $"解密配置失败({key})", LogBehavior.Toast); RemoveFromCache(key); Save(); return defaultValue; } } public bool HasValue(string key) => Content.Value.ContainsKey(key); public void RemoveFromCache(string key) { Content.Value.Remove(key); decryptedContentCache.TryRemove(key, out _); } public void ClearCache() { ResetContent(); decryptedContentCache.Clear(); } public void Save() => FileUtils.Write(filePath, Content.Value.ToString(Formatting.Indented)); } public static class Configs { public static readonly JsonConfigProvider AppData = new(Path.Combine(Paths.AppDataThenName, "config.json")); /// /// 从 secret.json 中读取特定密钥,如果未找到对应密钥则抛出异常。 /// public static string GetSecret(string key) => secret.Read(key)?.ToString() ?? throw new KeyNotFoundException($"Secret not found: {key}"); private static readonly JsonConfigProvider secret = new(Path.Combine(Paths.AppData, "secret.json")); } public class ConfigEntry(string key, T? defaultValue, IConfigProvider? defaultProvider = null, bool encrypted = false) where T : IEquatable { private IConfigProvider GetProvider(IConfigProvider? provider) => provider ?? defaultProvider ?? Configs.AppData; /// /// 当设置项的值实际被改变时触发,参数为新值。 /// public event Action? OnChanged; public T? Get(IConfigProvider? providerOverride = null) { var provider = GetProvider(providerOverride); if (!provider.HasValue(key)) return defaultValue; string? value = provider.Read(key, defaultValue?.ToString(), encrypted); try { if (value is null) return default; var type = typeof(T); if (type == typeof(string)) return (T) (object) value; if (typeof(JToken).IsAssignableFrom(type)) return (T) (object) value.DeserializeJson()!; return (T) Convert.ChangeType(value, type); } catch (Exception ex) { Logger.Error(ex, $"读取配置项失败({key})", LogBehavior.Toast); return defaultValue; } } public void Set(T? value, IConfigProvider? providerOverride = null) { var provider = GetProvider(providerOverride); T? current = Get(provider); if ((current is null && value is null) || (current is not null && value is not null && current.Equals(value))) return; // 值未改变,无需更新 provider.SetToCache(key, value?.ToString(), encrypted); provider.Save(); OnChanged?.Invoke(value); } public bool HasValue(IConfigProvider? providerOverride = null) { var provider = GetProvider(providerOverride); return provider.HasValue(key); } public void Reset(IConfigProvider? providerOverride = null) { var provider = GetProvider(providerOverride); if (!provider.HasValue(key)) return; // 值未设置,无需重置 provider.RemoveFromCache(key); provider.Save(); OnChanged?.Invoke(defaultValue); } public void Save(IConfigProvider? providerOverride = null) { var provider = GetProvider(providerOverride); provider.Save(); } }