using System.Text.Json;
using NewLife.Studio.Core.DTOs;
using NewLife.Studio.Store.Models;
using NewLife.Log;
namespace NewLife.Studio.Store;
/// <summary>基于 JSON 文件的本地存储服务实现</summary>
public class StoreService : IStoreService
{
private readonly string _basePath;
private readonly SecretProtection _protection;
private readonly JsonSerializerOptions _jsonOptions;
private readonly string _connectionsFile;
private readonly string _historyFile;
private readonly string _aiProfileFile;
private readonly string _preferencesFile;
private readonly SemaphoreSlim _lock = new(1, 1);
public StoreService() : this(new SecretProtection()) { }
public StoreService(SecretProtection protection)
: this(
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"NewLife.Studio"),
protection)
{
}
internal StoreService(string basePath, SecretProtection protection)
{
_protection = protection;
_jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
_basePath = basePath;
Directory.CreateDirectory(_basePath);
_connectionsFile = Path.Combine(_basePath, "connections.json");
_historyFile = Path.Combine(_basePath, "history.json");
_aiProfileFile = Path.Combine(_basePath, "ai_profile.json");
_preferencesFile = Path.Combine(_basePath, "preferences.json");
}
// ========== 连接管理 ==========
public async Task<List<ConnectionInfo>> ListConnectionsAsync()
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<List<StoredConnection>>(_connectionsFile) ?? [];
return stored.Select(s =>
{
var dto = s.ToDto();
// 解密密码字段
dto.ConnectionString = _protection.Unprotect(dto.ConnectionString);
return dto;
}).ToList();
}
finally
{
_lock.Release();
}
}
public async Task SaveConnectionAsync(ConnectionInfo conn)
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<List<StoredConnection>>(_connectionsFile) ?? [];
var existing = stored.FirstOrDefault(c => c.Id == conn.Id);
if (existing != null)
{
stored.Remove(existing);
}
// 加密存储
var toSave = StoredConnection.FromDto(conn);
toSave.ConnectionString = _protection.Protect(conn.ConnectionString);
stored.Add(toSave);
await SaveAsync(_connectionsFile, stored);
XTrace.WriteLine($"Store: Saved connection [{conn.Name}] ({conn.Id})");
}
finally
{
_lock.Release();
}
}
public async Task DeleteConnectionAsync(string id)
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<List<StoredConnection>>(_connectionsFile) ?? [];
stored.RemoveAll(c => c.Id == id);
await SaveAsync(_connectionsFile, stored);
XTrace.WriteLine($"Store: Deleted connection {id}");
}
finally
{
_lock.Release();
}
}
// ========== 查询历史 ==========
public async Task AddQueryHistoryAsync(QueryHistoryEntry entry)
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<StoredQueryHistory>(_historyFile) ?? new StoredQueryHistory();
stored.Entries.Insert(0, entry);
// 最多保留 500 条
if (stored.Entries.Count > 500)
{
stored.Entries = stored.Entries.Take(500).ToList();
}
await SaveAsync(_historyFile, stored);
}
finally
{
_lock.Release();
}
}
public async Task<List<QueryHistoryEntry>> GetRecentQueriesAsync(int count = 50)
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<StoredQueryHistory>(_historyFile);
return stored?.Entries.Take(count).ToList() ?? [];
}
finally
{
_lock.Release();
}
}
// ========== AI 配置 ==========
public async Task<AiProfile?> GetAiProfileAsync()
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<StoredAiProfile>(_aiProfileFile);
if (stored == null)
return null;
return new AiProfile
{
ProviderType = stored.ProviderType,
Endpoint = stored.Endpoint,
ApiKey = _protection.Unprotect(stored.ApiKey),
Model = stored.Model
};
}
finally
{
_lock.Release();
}
}
public async Task SaveAiProfileAsync(AiProfile profile)
{
await _lock.WaitAsync();
try
{
var stored = new StoredAiProfile
{
ProviderType = profile.ProviderType,
Endpoint = profile.Endpoint,
ApiKey = _protection.Protect(profile.ApiKey),
Model = profile.Model
};
await SaveAsync(_aiProfileFile, stored);
XTrace.WriteLine("Store: Saved AI profile");
}
finally
{
_lock.Release();
}
}
// ========== 应用偏好 ==========
public async Task<AppPreference> GetPreferencesAsync()
{
await _lock.WaitAsync();
try
{
var stored = await LoadAsync<StoredAppPreference>(_preferencesFile);
if (stored == null)
return new AppPreference();
return new AppPreference
{
MaxRows = stored.MaxRows,
DefaultExportPath = stored.DefaultExportPath,
Theme = stored.Theme,
Language = stored.Language
};
}
finally
{
_lock.Release();
}
}
public async Task SavePreferencesAsync(AppPreference pref)
{
await _lock.WaitAsync();
try
{
await SaveAsync(_preferencesFile, new StoredAppPreference
{
MaxRows = pref.MaxRows,
DefaultExportPath = pref.DefaultExportPath,
Theme = pref.Theme,
Language = pref.Language
});
XTrace.WriteLine("Store: Saved preferences");
}
finally
{
_lock.Release();
}
}
// ========== 底层 JSON 读写 ==========
private async Task<T?> LoadAsync<T>(string filePath) where T : class
{
if (!File.Exists(filePath))
return null;
var json = await File.ReadAllTextAsync(filePath);
return JsonSerializer.Deserialize<T>(json, _jsonOptions);
}
private async Task SaveAsync<T>(string filePath, T data)
{
var json = JsonSerializer.Serialize(data, _jsonOptions);
await File.WriteAllTextAsync(filePath, json);
}
}
|