feat: 初始化NewLife Studio项目,完成基础框架与数据管理模块
何炳宏 authored at 2026-05-26 12:09:09
7.23 KiB
NewLife.Studio
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);
    }
}