using System.Text.Json; using Microsoft.Extensions.Options; using MiGu.Server.Launcher; namespace MiGu.Server.Ota; public sealed class OtaVehicleSource { private readonly IHttpClientFactory _httpFactory; private readonly Simple3Options _sl; private readonly InternalTokenStoreAccessor _token; private readonly ILogger _log; public OtaVehicleSource( IHttpClientFactory httpFactory, IOptions sl, InternalTokenStoreAccessor token, ILogger log) { _httpFactory = httpFactory; _sl = sl.Value; _token = token; _log = log; } public async Task> ListCarsAsync(CancellationToken ct) { var port = _sl.ProjectionPort > 0 ? _sl.ProjectionPort : 8222; var cars = await TryProjectionAsync(port, ct); if (cars.Count == 0) cars = await TryAgvListAsync(port, ct); return cars; } private async Task> TryProjectionAsync(int port, CancellationToken ct) { try { using var client = _httpFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(8); using var req = new HttpRequestMessage(HttpMethod.Get, $"http://127.0.0.1:{port}/projection/cars"); var token = _token.Token; if (!string.IsNullOrEmpty(token)) req.Headers.TryAddWithoutValidation("X-Platform-Internal-Token", token); using var resp = await client.SendAsync(req, ct); if (!resp.IsSuccessStatusCode) return new(); var text = await resp.Content.ReadAsStringAsync(ct); return ParseCars(text); } catch (Exception ex) { _log.LogDebug(ex, "projection/cars failed"); return new(); } } private async Task> TryAgvListAsync(int port, CancellationToken ct) { try { using var client = _httpFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(8); using var resp = await client.GetAsync($"http://127.0.0.1:{port}/api/agv/list", ct); if (!resp.IsSuccessStatusCode) return new(); var text = await resp.Content.ReadAsStringAsync(ct); using var doc = JsonDocument.Parse(text); var root = doc.RootElement; var arr = root.ValueKind == JsonValueKind.Array ? root : root.TryGetProperty("data", out var d) ? d : root.TryGetProperty("items", out var i) ? i : default; if (arr.ValueKind != JsonValueKind.Array) return new(); var list = new List(); foreach (var el in arr.EnumerateArray()) { var id = GetStr(el, "agv_id", "id", "carId") ?? ""; var name = GetStr(el, "agv_name", "name") ?? id; var ip = GetStr(el, "agv_ip", "ip"); var state = GetStr(el, "status", "state"); if (string.IsNullOrEmpty(id) && string.IsNullOrEmpty(ip)) continue; list.Add(new OtaVehicleRow { Id = string.IsNullOrEmpty(id) ? ip! : id, Name = name, Ip = ip, State = state }); } return list; } catch (Exception ex) { _log.LogDebug(ex, "agv/list failed"); return new(); } } private static List ParseCars(string text) { using var doc = JsonDocument.Parse(text); var root = doc.RootElement; var arr = root.ValueKind == JsonValueKind.Array ? root : root.TryGetProperty("cars", out var c) ? c : root.TryGetProperty("items", out var i) ? i : root.TryGetProperty("data", out var d) ? d : default; if (arr.ValueKind != JsonValueKind.Array) return new(); var list = new List(); foreach (var el in arr.EnumerateArray()) { var id = GetStr(el, "id", "carId", "rawId") ?? ""; var name = GetStr(el, "name") ?? id; var ip = GetStr(el, "ip"); var state = GetStr(el, "state", "status"); var group = GetStr(el, "group"); if (string.IsNullOrEmpty(id)) continue; list.Add(new OtaVehicleRow { Id = id, Name = name, Ip = ip, State = state, Group = group }); } return list; } private static string? GetStr(JsonElement el, params string[] names) { foreach (var n in names) { if (el.TryGetProperty(n, out var p) && p.ValueKind == JsonValueKind.String) return p.GetString(); if (el.TryGetProperty(n, out p) && p.ValueKind is JsonValueKind.Number) return p.ToString(); } return null; } } /// 避免 Ota 层直接依赖 Auth 命名空间循环;薄包装 InternalTokenStore。 public sealed class InternalTokenStoreAccessor { private readonly Auth.InternalTokenStore _store; public InternalTokenStoreAccessor(Auth.InternalTokenStore store) => _store = store; public string Token => _store.Token; }