using Microsoft.Extensions.Configuration; using System.Net.Http; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Web; using YY.Admin.Core; using YY.Admin.Core.Entity; using YY.Admin.Core.Services; namespace YY.Admin.Services.Service.MixerMaterial; public class MixerMaterialService : IMixerMaterialService, ISingletonDependency { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; private readonly ILoggerService _logger; private static readonly JsonSerializerOptions _jsonOpts = new() { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Converters = { new NullableDateTimeJsonConverter() } }; public MixerMaterialService( IHttpClientFactory httpClientFactory, IConfiguration configuration, ILoggerService logger) { _httpClientFactory = httpClientFactory; _configuration = configuration; _logger = logger; } private string BaseUrl => (_configuration.GetValue("JeecgIntegration:BaseUrl") ?? "http://localhost:8080/jeecg-boot").TrimEnd('/'); private HttpClient CreateClient() => _httpClientFactory.CreateClient("JeecgApi"); public async Task PageAsync( int pageNo, int pageSize, string? materialCode = null, string? materialName = null, string? erpCode = null, string? majorCategoryId = null, string? minorCategoryId = null, CancellationToken ct = default) { var query = HttpUtility.ParseQueryString(string.Empty); query["pageNo"] = pageNo.ToString(); query["pageSize"] = pageSize.ToString(); if (!string.IsNullOrWhiteSpace(materialCode)) query["materialCode"] = materialCode; if (!string.IsNullOrWhiteSpace(materialName)) query["materialName"] = materialName; if (!string.IsNullOrWhiteSpace(erpCode)) query["erpCode"] = erpCode; if (!string.IsNullOrWhiteSpace(majorCategoryId)) query["majorCategoryId"] = majorCategoryId; if (!string.IsNullOrWhiteSpace(minorCategoryId)) query["minorCategoryId"] = minorCategoryId; var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/list?{query}"; using var client = CreateClient(); 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); var result = doc.RootElement.GetProperty("result"); var records = result.GetProperty("records").Deserialize>(_jsonOpts) ?? new(); var total = result.TryGetProperty("total", out var totalEl) ? totalEl.GetInt64() : records.Count; return new MixerMaterialPageResult(records, total, pageNo, pageSize); } public async Task GetByIdAsync(string id, CancellationToken ct = default) { var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/queryById?id={Uri.EscapeDataString(id)}"; using var client = CreateClient(); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return null; var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); if (!doc.RootElement.TryGetProperty("result", out var resultEl)) return null; return resultEl.Deserialize(_jsonOpts); } public Task AddAsync(MesMixerMaterial material, CancellationToken ct = default) => PostJsonAsync($"{BaseUrl}/mes/material/mixerMaterial/anon/add", material, ct); public Task EditAsync(MesMixerMaterial material, CancellationToken ct = default) => PostJsonAsync($"{BaseUrl}/mes/material/mixerMaterial/anon/edit", material, ct); public async Task DeleteAsync(string id, CancellationToken ct = default) { var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/delete?id={Uri.EscapeDataString(id)}"; using var client = CreateClient(); var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false); return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); } public async Task DeleteBatchAsync(string ids, CancellationToken ct = default) { var url = $"{BaseUrl}/mes/material/mixerMaterial/anon/deleteBatch?ids={Uri.EscapeDataString(ids)}"; using var client = CreateClient(); var resp = await client.DeleteAsync(url, ct).ConfigureAwait(false); return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); } public async Task>> GetMajorCategoryOptionsAsync(CancellationToken ct = default) { var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pcode=XSLMES_MATERIAL"; using var client = CreateClient(); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return []; var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; return ParseTreeOptions(arr); } public async Task>> GetMinorCategoryOptionsAsync(string majorCategoryId, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(majorCategoryId)) return []; var url = $"{BaseUrl}/sys/category/anon/loadTreeData?pid={Uri.EscapeDataString(majorCategoryId)}"; using var client = CreateClient(); var resp = await client.GetAsync(url, ct).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) return []; var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); var arr = doc.RootElement.TryGetProperty("result", out var resultEl) ? resultEl : doc.RootElement; return ParseTreeOptions(arr); } private static List> ParseTreeOptions(JsonElement root) { var list = new List>(); if (root.ValueKind != JsonValueKind.Array) return list; foreach (var item in root.EnumerateArray()) { var text = item.TryGetProperty("title", out var titleEl) ? titleEl.GetString() : null; var key = item.TryGetProperty("key", out var keyEl) ? keyEl.GetString() : null; if (!string.IsNullOrWhiteSpace(text) && !string.IsNullOrWhiteSpace(key)) { list.Add(new KeyValuePair(text!, key!)); } } return list; } private async Task PostJsonAsync(string url, object body, CancellationToken ct) { var content = new StringContent(JsonSerializer.Serialize(body, _jsonOpts), Encoding.UTF8, "application/json"); using var client = CreateClient(); var resp = await client.PostAsync(url, content, ct).ConfigureAwait(false); return resp.IsSuccessStatusCode && await IsSuccessResultAsync(resp, ct).ConfigureAwait(false); } private static async Task IsSuccessResultAsync(HttpResponseMessage resp, CancellationToken ct) { try { var json = await resp.Content.ReadAsStringAsync(ct).ConfigureAwait(false); using var doc = JsonDocument.Parse(json); if (doc.RootElement.TryGetProperty("code", out var code)) { return code.GetInt32() == 200; } if (doc.RootElement.TryGetProperty("success", out var success)) { return success.GetBoolean(); } return true; } catch { return true; } } private sealed class NullableDateTimeJsonConverter : JsonConverter { private static readonly string[] SupportedFormats = [ "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, SupportedFormats, 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(); } } }