Files
Migu2.0/MiGu.Server/Ota/OtaVehicleSource.cs
T
黄兆尉andCursor 3686abdc78 将调度内核标识从 SimpleLite 全面重命名为 Simple3。
配置段/环境变量、Launcher、健康检查 API、OpenAPI 与前后端文案同步;兼容探测旧 SimpleLite 进程名。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-26 17:46:52 +08:00

142 lines
5.2 KiB
C#

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<OtaVehicleSource> _log;
public OtaVehicleSource(
IHttpClientFactory httpFactory,
IOptions<Simple3Options> sl,
InternalTokenStoreAccessor token,
ILogger<OtaVehicleSource> log)
{
_httpFactory = httpFactory;
_sl = sl.Value;
_token = token;
_log = log;
}
public async Task<List<OtaVehicleRow>> 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<List<OtaVehicleRow>> 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<List<OtaVehicleRow>> 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<OtaVehicleRow>();
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<OtaVehicleRow> 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<OtaVehicleRow>();
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;
}
}
/// <summary>避免 Ota 层直接依赖 Auth 命名空间循环;薄包装 InternalTokenStore。</summary>
public sealed class InternalTokenStoreAccessor
{
private readonly Auth.InternalTokenStore _store;
public InternalTokenStoreAccessor(Auth.InternalTokenStore store) => _store = store;
public string Token => _store.Token;
}