using Microsoft.Extensions.Configuration; using System.Net.Http; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using YY.Admin.Core; using YY.Admin.Core.Entity; using YY.Admin.Core.Services; namespace YY.Admin.Services.Service.Warehouse; /// /// 仓库数据只读服务:启动时从后端拉取全量列表并缓存到磁盘,断网时回退本地缓存。 /// 仅供其他模块的下拉筛选使用,不提供 CRUD。 /// public class WarehouseService : IWarehouseService, ISingletonDependency { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly INetworkMonitor _networkMonitor; private readonly ILoggerService _logger; private readonly object _cacheLock = new(); private readonly string _cacheFilePath; private List _localCache = new(); private static readonly JsonSerializerOptions _jsonOpts = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; public WarehouseService( IHttpClientFactory httpClientFactory, IConfiguration configuration, INetworkMonitor networkMonitor, ILoggerService logger) { _httpClientFactory = httpClientFactory; _configuration = configuration; _networkMonitor = networkMonitor; _logger = logger; var appDataDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "sync-cache"); Directory.CreateDirectory(appDataDir); _cacheFilePath = Path.Combine(appDataDir, "warehouse-cache.json"); LoadCacheFromDisk(); _logger.Information($"[仓库数据] 服务初始化,本地缓存={_localCache.Count} 条"); if (_networkMonitor.IsOnline) _ = Task.Run(() => RefreshFromRemoteAsync(CancellationToken.None)); _networkMonitor.StatusChanged += isOnline => { if (isOnline) _ = Task.Run(() => RefreshFromRemoteAsync(CancellationToken.None)); }; } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private int DefaultTenantId => (int?)_configuration.GetValue("JeecgIntegration:DefaultTenantId") ?? 1002; public async Task> GetAllAsync(CancellationToken ct = default) { if (_networkMonitor.IsOnline) { try { await RefreshFromRemoteAsync(ct).ConfigureAwait(false); } catch { } } lock (_cacheLock) { return _localCache.ToList(); } } private async Task RefreshFromRemoteAsync(CancellationToken ct) { try { var result = new List(); int pageNo = 1; const int pageSize = 500; while (true) { var url = $"{BaseUrl}/xslmes/mesXslWarehouse/anon/list?pageNo={pageNo}&pageSize={pageSize}&tenantId={DefaultTenantId}"; using var client = _httpClientFactory.CreateClient("JeecgApi"); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); if (!doc.RootElement.TryGetProperty("result", out var resultEl)) break; if (resultEl.TryGetProperty("records", out var recordsEl)) { var page = recordsEl.Deserialize>(_jsonOpts); if (page != null) result.AddRange(page); } long total = 0; if (resultEl.TryGetProperty("total", out var totalEl)) total = totalEl.GetInt64(); if (result.Count >= total || (resultEl.TryGetProperty("records", out var r2) && r2.GetArrayLength() < pageSize)) break; pageNo++; } lock (_cacheLock) { _localCache = result; SaveCacheToDiskUnsafe(); } _logger.Information($"[仓库数据] 远端刷新成功,共 {result.Count} 条"); } catch (Exception ex) { _logger.Warning($"[仓库数据] 远端刷新失败,使用缓存: {ex.Message}"); } } private void LoadCacheFromDisk() { try { if (!File.Exists(_cacheFilePath)) return; var json = File.ReadAllText(_cacheFilePath); _localCache = JsonSerializer.Deserialize>(json, _jsonOpts) ?? new(); } catch { _localCache = new(); } } private void SaveCacheToDiskUnsafe() { try { File.WriteAllText(_cacheFilePath, JsonSerializer.Serialize(_localCache, _jsonOpts)); } catch { } } }