博主头像
阿柒の站

一名躺平程序员,每天只想着躺平
(┬┬﹏┬┬)

C# 读写配置文件方案

一个防修改、保持完整性的读写配置文件方案。

ConfigManager.cs

public class ConfigManager
{
    private static readonly string ConfigPath = Path.Combine(AppContext.BaseDirectory, "config.dat"); // 配置文件路径,默认存储在程序根目录
    private static readonly byte[] HmacKey = Encoding.UTF8.GetBytes("zzjs_key"); // 加密密钥

    private Dictionary<string, object> _configData;
    private readonly object _lock = new();

    public static ConfigManager Instance { get; } = new ConfigManager();

    /// <summary>
    /// 构造函数
    /// </summary>
    private ConfigManager()
    {
        Load();
    }

    /// <summary>
    /// 索引器
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public object? this[string key]
    {
        get => Get(key);
        set => Set(key, value);
    }

    /// <summary>
    /// 获取配置项
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public object? Get(string key)
    {
        var sections = key.Split(':', StringSplitOptions.RemoveEmptyEntries);
        object? current = _configData;

        foreach (var section in sections)
        {
            if (current is Dictionary<string, object> dict && dict.TryGetValue(section, out var next))
                current = next;
            else
                return null;
        }

        return current;
    }

    /// <summary>
    /// 设置配置项
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public void Set(string key, object? value)
    {
        var sections = key.Split(':', StringSplitOptions.RemoveEmptyEntries);
        lock (_lock)
        {
            var current = _configData;
            for (int i = 0; i < sections.Length - 1; i++)
            {
                var section = sections[i];
                if (!current.TryGetValue(section, out var sub) || sub is not Dictionary<string, object>)
                {
                    current[section] = new Dictionary<string, object>();
                }
                current = (Dictionary<string, object>)current[section];
            }

            current[sections.Last()] = value!;
        }
    }

    /// <summary>
    /// 存储配置文件
    /// </summary>
    public void Save()
    {
        lock (_lock)
        {
            var json = JsonSerializer.Serialize(_configData);
            var compressed = Compress(Encoding.UTF8.GetBytes(json));
            var signature = HMACSHA256.HashData(HmacKey, compressed);
            var finalData = signature.Concat(compressed).ToArray();
            File.WriteAllBytes(ConfigPath, finalData);
        }
    }

    /// <summary>
    /// 加载配置文件
    /// </summary>
    private void Load()
    {
        try
        {
            if (!File.Exists(ConfigPath))
            {
                // 如果配置文件不存在,可以选择创建一个默认配置
                _configData = [];
                Save();
                //throw new FileNotFoundException();
            }

            var bytes = File.ReadAllBytes(ConfigPath);
            if (bytes.Length < 32)
                throw new Exception("配置文件不完整");

            var signature = bytes.Take(32).ToArray();
            var content = bytes.Skip(32).ToArray();
            var actualSignature = HMACSHA256.HashData(HmacKey, content);

            if (!signature.SequenceEqual(actualSignature))
                throw new Exception("配置文件已被篡改");

            var decompressed = Decompress(content);
            _configData = JsonSerializer.Deserialize<Dictionary<string, object>>(decompressed)!;
        }
        catch
        {
            // 可以在这里加载默认配置

            _configData = [];
            System.Windows.MessageBox.Show("配置文件损坏或不存在,已加载默认配置", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
        }
    }

    /// <summary>
    /// 压缩配置文件
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static byte[] Compress(byte[] data)
    {
        using var output = new MemoryStream();
        using (var gz = new GZipStream(output, CompressionLevel.Optimal))
            gz.Write(data, 0, data.Length);
        return output.ToArray();
    }

    /// <summary>
    /// 解压缩配置文件
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private static string Decompress(byte[] data)
    {
        using var input = new MemoryStream(data);
        using var gz = new GZipStream(input, CompressionMode.Decompress);
        using var reader = new StreamReader(gz, Encoding.UTF8);
        return reader.ReadToEnd();
    }
}

SysConfig.cs

/// <summary>
/// 系统配置管理
/// </summary>
public static class SysConfig
{
    /// <summary>
    /// 获取配置项
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public static string? Get(string key)=> Get<string>(key);

    /// <summary>
    /// 获取配置项
    /// </summary>
    public static T? Get<T>(string key)
    {
        var value = ConfigManager.Instance.Get(key);
        if (value == null) return default;

        try
        {
            if (value is JsonElement jsonElement)
            {
                value = jsonElement.ToString();
            }
            return (T)Convert.ChangeType(value, typeof(T));
        }
        catch
        {
            return default;
        }
    }

    /// <summary>
    /// 设置配置项
    /// </summary>
    public static void Set(string key, object? value)
    {
        ConfigManager.Instance.Set(key, value);
    }

    /// <summary>
    /// 设置配置项并保存
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetAndSave(string key, object? value)
    {
        ConfigManager.Instance.Set(key, value);
        ConfigManager.Instance.Save();
    }

    /// <summary>
    /// 保存配置
    /// </summary>
    public static void Save()
    {
        ConfigManager.Instance.Save();
    }
}

使用示例:

// 获取
SysConfig.Get("FirstConfig");
SysConfig.Get<int>("ConvertIntConfig");
SysConfig.Get<bool>("Convert:FirstBoolVal");

// 设置
SysConfig.Set("FirstConfig", "保存值");
SysConfig.Set("Convert:FirstBoolVal", true);

// 保存配置项
SysConfig.Save();

// 上述[设置]和[保存配置项]可使用下面一步完成
SysConfig.SetAndSave("ConvertIntConfig", 100);
C# 读写配置文件方案
https://wc.sb/22
本文作者 阿柒
发布时间 2025-09-11
许可协议 CC BY-NC-SA 4.0
发表新评论