233 lines
7.4 KiB
C#
233 lines
7.4 KiB
C#
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
using WorkCameraExport.Models;
|
||
using WorkCameraExport.Services.Interfaces;
|
||
|
||
namespace WorkCameraExport.Services
|
||
{
|
||
/// <summary>
|
||
/// 配置服务实现 - 负责配置文件和登录凭证的管理
|
||
/// </summary>
|
||
public class ConfigService : IConfigService
|
||
{
|
||
private readonly string _appDataPath;
|
||
private readonly string _configFilePath;
|
||
private readonly string _credentialsFilePath;
|
||
private readonly string _tempPath;
|
||
private readonly string _logPath;
|
||
|
||
// 用于加密凭证的密钥(AES-256 需要 32 字节密钥,16 字节 IV)
|
||
private static readonly byte[] EncryptionKey = Encoding.UTF8.GetBytes("WorkCameraExport2025SecretKey!!!"); // 32 bytes
|
||
private static readonly byte[] EncryptionIV = Encoding.UTF8.GetBytes("WCE2025InitVect!"); // 16 bytes
|
||
|
||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||
{
|
||
WriteIndented = true,
|
||
PropertyNameCaseInsensitive = true
|
||
};
|
||
|
||
public ConfigService()
|
||
{
|
||
_appDataPath = Path.Combine(
|
||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||
"WorkCameraExport");
|
||
_configFilePath = Path.Combine(_appDataPath, "config.json");
|
||
_credentialsFilePath = Path.Combine(_appDataPath, "credentials.dat");
|
||
_tempPath = Path.Combine(_appDataPath, "temp");
|
||
_logPath = Path.Combine(_appDataPath, "logs");
|
||
|
||
EnsureDirectoriesExist();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 用于测试的构造函数,允许指定自定义路径
|
||
/// </summary>
|
||
public ConfigService(string appDataPath)
|
||
{
|
||
_appDataPath = appDataPath;
|
||
_configFilePath = Path.Combine(_appDataPath, "config.json");
|
||
_credentialsFilePath = Path.Combine(_appDataPath, "credentials.dat");
|
||
_tempPath = Path.Combine(_appDataPath, "temp");
|
||
_logPath = Path.Combine(_appDataPath, "logs");
|
||
|
||
EnsureDirectoriesExist();
|
||
}
|
||
|
||
#region IConfigService 实现
|
||
|
||
public string AppDataPath => _appDataPath;
|
||
public string TempPath => _tempPath;
|
||
public string LogPath => _logPath;
|
||
|
||
public bool HasSavedCredentials => File.Exists(_credentialsFilePath);
|
||
|
||
public AppConfig LoadConfig()
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(_configFilePath))
|
||
{
|
||
var json = File.ReadAllText(_configFilePath);
|
||
var config = JsonSerializer.Deserialize<AppConfig>(json, JsonOptions);
|
||
if (config != null)
|
||
{
|
||
config.Validate();
|
||
return config;
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 加载失败时返回默认配置
|
||
}
|
||
|
||
return AppConfig.CreateDefault();
|
||
}
|
||
|
||
public void SaveConfig(AppConfig config)
|
||
{
|
||
config.Validate();
|
||
var json = JsonSerializer.Serialize(config, JsonOptions);
|
||
File.WriteAllText(_configFilePath, json);
|
||
}
|
||
|
||
public void SaveCredentials(string serverUrl, string token, string username)
|
||
{
|
||
var credentials = new CredentialsData
|
||
{
|
||
ServerUrl = serverUrl,
|
||
Token = token,
|
||
Username = username,
|
||
SavedAt = DateTime.Now
|
||
};
|
||
|
||
var json = JsonSerializer.Serialize(credentials, JsonOptions);
|
||
var encrypted = Encrypt(json);
|
||
File.WriteAllBytes(_credentialsFilePath, encrypted);
|
||
}
|
||
|
||
public (string ServerUrl, string Token, string Username)? LoadCredentials()
|
||
{
|
||
try
|
||
{
|
||
if (!File.Exists(_credentialsFilePath))
|
||
return null;
|
||
|
||
var encrypted = File.ReadAllBytes(_credentialsFilePath);
|
||
var json = Decrypt(encrypted);
|
||
var credentials = JsonSerializer.Deserialize<CredentialsData>(json, JsonOptions);
|
||
|
||
if (credentials != null &&
|
||
!string.IsNullOrEmpty(credentials.ServerUrl) &&
|
||
!string.IsNullOrEmpty(credentials.Token))
|
||
{
|
||
return (credentials.ServerUrl, credentials.Token, credentials.Username);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 解密失败时清除凭证
|
||
ClearCredentials();
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
public void ClearCredentials()
|
||
{
|
||
try
|
||
{
|
||
if (File.Exists(_credentialsFilePath))
|
||
{
|
||
File.Delete(_credentialsFilePath);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略删除错误
|
||
}
|
||
}
|
||
|
||
public void CleanTempFiles()
|
||
{
|
||
try
|
||
{
|
||
if (Directory.Exists(_tempPath))
|
||
{
|
||
var directory = new DirectoryInfo(_tempPath);
|
||
foreach (var file in directory.GetFiles())
|
||
{
|
||
try { file.Delete(); } catch { }
|
||
}
|
||
foreach (var dir in directory.GetDirectories())
|
||
{
|
||
try { dir.Delete(true); } catch { }
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略清理错误
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 私有方法
|
||
|
||
private void EnsureDirectoriesExist()
|
||
{
|
||
if (!Directory.Exists(_appDataPath))
|
||
Directory.CreateDirectory(_appDataPath);
|
||
if (!Directory.Exists(_tempPath))
|
||
Directory.CreateDirectory(_tempPath);
|
||
if (!Directory.Exists(_logPath))
|
||
Directory.CreateDirectory(_logPath);
|
||
}
|
||
|
||
private static byte[] Encrypt(string plainText)
|
||
{
|
||
using var aes = Aes.Create();
|
||
aes.Key = EncryptionKey;
|
||
aes.IV = EncryptionIV;
|
||
|
||
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
|
||
using var ms = new MemoryStream();
|
||
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
|
||
using (var sw = new StreamWriter(cs))
|
||
{
|
||
sw.Write(plainText);
|
||
}
|
||
return ms.ToArray();
|
||
}
|
||
|
||
private static string Decrypt(byte[] cipherText)
|
||
{
|
||
using var aes = Aes.Create();
|
||
aes.Key = EncryptionKey;
|
||
aes.IV = EncryptionIV;
|
||
|
||
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
|
||
using var ms = new MemoryStream(cipherText);
|
||
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
|
||
using var sr = new StreamReader(cs);
|
||
return sr.ReadToEnd();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 内部类
|
||
|
||
private class CredentialsData
|
||
{
|
||
public string ServerUrl { get; set; } = "";
|
||
public string Token { get; set; } = "";
|
||
public string Username { get; set; } = "";
|
||
public DateTime SavedAt { get; set; }
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|