覆盖包库回传、任务下发、CDM 任务同步与报警采集,并为包/任务 ID 与上传文件名加上路径安全校验。 Co-authored-by: Cursor <cursoragent@cursor.com>
142 lines
5.2 KiB
C#
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 SimpleLiteOptions _sl;
|
|
private readonly InternalTokenStoreAccessor _token;
|
|
private readonly ILogger<OtaVehicleSource> _log;
|
|
|
|
public OtaVehicleSource(
|
|
IHttpClientFactory httpFactory,
|
|
IOptions<SimpleLiteOptions> 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;
|
|
}
|