新增打印模板管理功能,包含免密接口和实时通知机制,支持桌面端打印模板的查询和列表展示。更新相关控制器、服务和视图,优化用户体验并增强系统的实时数据同步能力。

This commit is contained in:
geht
2026-05-12 18:29:03 +08:00
parent f5ba828eff
commit fcedc66f7a
32 changed files with 2788 additions and 2 deletions

View File

@@ -0,0 +1,170 @@
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 YY.Admin.Core.Entity;
using YY.Admin.Core.Services;
namespace YY.Admin.Services.Service.Print;
public class PrintTemplateService : IPrintTemplateService
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly IConfiguration _configuration;
private readonly object _cacheLock = new();
private List<PrintTemplate> _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<string>("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/');
public PrintTemplateService(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-template-cache.json");
LoadCacheFromDisk();
}
public IReadOnlyList<PrintTemplate> GetCached()
{
lock (_cacheLock)
{
return _localCache.AsReadOnly();
}
}
public async Task<PrintTemplate?> GetByCodeAsync(string templateCode, CancellationToken ct = default)
{
try
{
var client = _httpClientFactory.CreateClient("JeecgApi");
var url = $"{BaseUrl}/print/template/anon/queryByCode?code={Uri.EscapeDataString(templateCode)}";
var response = await client.GetAsync(url, ct).ConfigureAwait(false);
if (!response.IsSuccessStatusCode) return GetCachedByCode(templateCode);
var result = await response.Content.ReadFromJsonAsync<JeecgResult<PrintTemplate>>(JsonOpts, ct).ConfigureAwait(false);
return result?.Success == true ? result.Result : GetCachedByCode(templateCode);
}
catch
{
return GetCachedByCode(templateCode);
}
}
public async Task<IReadOnlyList<PrintTemplate>> ListAsync(CancellationToken ct = default)
{
try
{
return await RefreshCacheAsync(ct).ConfigureAwait(false);
}
catch
{
return GetCached();
}
}
public async Task<IReadOnlyList<PrintTemplate>> RefreshCacheAsync(CancellationToken ct = default)
{
var client = _httpClientFactory.CreateClient("JeecgApi");
var url = $"{BaseUrl}/print/template/anon/list?pageSize=200";
var response = await client.GetAsync(url, ct).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<JeecgResult<JeecgPage<PrintTemplate>>>(JsonOpts, ct).ConfigureAwait(false);
if (result?.Success != true)
throw new InvalidOperationException(result?.Message ?? "后端返回失败");
var records = result.Result?.Records ?? new List<PrintTemplate>();
lock (_cacheLock)
{
_localCache = records;
SaveCacheToDiskUnsafe();
}
return records.AsReadOnly();
}
private PrintTemplate? GetCachedByCode(string code)
{
lock (_cacheLock)
{
return _localCache.FirstOrDefault(t =>
string.Equals(t.TemplateCode, code, StringComparison.OrdinalIgnoreCase));
}
}
private void LoadCacheFromDisk()
{
try
{
if (!File.Exists(_cacheFilePath)) return;
var json = File.ReadAllText(_cacheFilePath);
var data = JsonSerializer.Deserialize<List<PrintTemplate>>(json, JsonOpts);
_localCache = data ?? new List<PrintTemplate>();
}
catch
{
_localCache = new List<PrintTemplate>();
}
}
private void SaveCacheToDiskUnsafe()
{
try
{
var json = JsonSerializer.Serialize(_localCache, JsonOpts);
File.WriteAllText(_cacheFilePath, json);
}
catch { }
}
private record JeecgResult<T>(bool Success, T? Result, string? Message);
private record JeecgPage<T>(List<T> Records, long Total);
private sealed class NullableDateTimeJsonConverter : JsonConverter<DateTime?>
{
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();
}
}
}