using System.IO; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Http; using YY.Admin.Core.Entity; using YY.Admin.Core.Services; namespace YY.Admin.Services.Service.Print; /// 业务打印绑定:免密列表 + 本地缓存(只读) public class PrintBizTemplateBindService : IPrintBizTemplateBindService { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly object _cacheLock = new(); private List _localCache = new(); private readonly string _cacheFilePath; private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new NullableDateTimeJsonConverter() } }; private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); public PrintBizTemplateBindService(IHttpClientFactory httpClientFactory, IConfiguration configuration) { _httpClientFactory = httpClientFactory; _configuration = configuration; var appDataDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "YY.Admin", "sync-cache"); Directory.CreateDirectory(appDataDir); _cacheFilePath = Path.Combine(appDataDir, "print-biz-bind-cache.json"); LoadCacheFromDisk(); } public IReadOnlyList GetCached() { lock (_cacheLock) { return _localCache.AsReadOnly(); } } public async Task> ListAsync(CancellationToken ct = default) { try { return await RefreshCacheAsync(ct).ConfigureAwait(false); } catch { return GetCached(); } } public async Task> RefreshCacheAsync(CancellationToken ct = default) { var client = _httpClientFactory.CreateClient("JeecgApi"); var url = $"{BaseUrl}/print/bizTemplateBind/anon/list?pageSize=500&pageNo=1"; var response = await client.GetAsync(url, ct).ConfigureAwait(false); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync>>(JsonOpts, ct).ConfigureAwait(false); if (result?.Success != true) throw new InvalidOperationException(result?.Message ?? "后端返回失败"); var records = result.Result?.Records ?? new List(); lock (_cacheLock) { _localCache = records; SaveCacheToDiskUnsafe(); } return records.AsReadOnly(); } public async Task GetByIdAsync(string id, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(id)) return null; try { var client = _httpClientFactory.CreateClient("JeecgApi"); var url = $"{BaseUrl}/print/bizTemplateBind/anon/queryById?id={Uri.EscapeDataString(id)}"; var response = await client.GetAsync(url, ct).ConfigureAwait(false); if (!response.IsSuccessStatusCode) return GetCachedById(id); var result = await response.Content.ReadFromJsonAsync>(JsonOpts, ct).ConfigureAwait(false); return result?.Success == true ? result.Result : GetCachedById(id); } catch { return GetCachedById(id); } } private PrintBizTemplateBind? GetCachedById(string id) { lock (_cacheLock) { return _localCache.FirstOrDefault(x => string.Equals(x.Id, id, StringComparison.OrdinalIgnoreCase)); } } private void LoadCacheFromDisk() { try { if (!File.Exists(_cacheFilePath)) return; var json = File.ReadAllText(_cacheFilePath); var data = JsonSerializer.Deserialize>(json, JsonOpts); _localCache = data ?? new List(); } catch { _localCache = new List(); } } private void SaveCacheToDiskUnsafe() { try { var json = JsonSerializer.Serialize(_localCache, JsonOpts); File.WriteAllText(_cacheFilePath, json); } catch { /* 离线或磁盘异常时忽略 */ } } private record JeecgResult(bool Success, T? Result, string? Message); private record JeecgPage(List Records, long Total); private sealed class NullableDateTimeJsonConverter : JsonConverter { private static readonly string[] Formats = [ "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ss.fff", "yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ" ]; public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) return null; if (reader.TokenType == JsonTokenType.String) { var raw = reader.GetString(); if (string.IsNullOrWhiteSpace(raw)) return null; if (DateTime.TryParseExact(raw, Formats, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AssumeLocal, out var exact)) return exact; if (DateTime.TryParse(raw, out var fallback)) return fallback; } throw new JsonException($"无法将 JSON 值转换为 DateTime?,token={reader.TokenType}"); } public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) { if (value.HasValue) writer.WriteStringValue(value.Value.ToString("yyyy-MM-dd HH:mm:ss")); else writer.WriteNullValue(); } } }