119 lines
3.9 KiB
C#
119 lines
3.9 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
|
|
namespace EpsonPrintService;
|
|
|
|
public sealed class PuduRobotClient
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly string _baseUrl;
|
|
private readonly string _username;
|
|
private readonly string _password;
|
|
private string? _token;
|
|
|
|
public PuduRobotClient(string baseUrl, string username, string password, HttpClient? http = null)
|
|
{
|
|
_baseUrl = baseUrl.TrimEnd('/');
|
|
_username = username;
|
|
_password = password;
|
|
_http = http ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
|
}
|
|
|
|
public async Task SendToTableAsync(string robotSn, string targetPoint, CancellationToken ct = default)
|
|
{
|
|
await EnsureTokenAsync(ct);
|
|
try
|
|
{
|
|
await PostAddCommandAsync(robotSn, targetPoint, ct);
|
|
}
|
|
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
|
{
|
|
// Token expired — refresh once and retry.
|
|
_token = null;
|
|
await EnsureTokenAsync(ct);
|
|
await PostAddCommandAsync(robotSn, targetPoint, ct);
|
|
}
|
|
}
|
|
|
|
private async Task EnsureTokenAsync(CancellationToken ct)
|
|
{
|
|
if (_token is not null) return;
|
|
|
|
using var resp = await _http.PostAsJsonAsync(
|
|
$"{_baseUrl}/api/Auth/login",
|
|
new { username = _username, password = _password },
|
|
ct);
|
|
resp.EnsureSuccessStatusCode();
|
|
|
|
var bodyText = await resp.Content.ReadAsStringAsync(ct);
|
|
var token = ExtractToken(bodyText);
|
|
if (string.IsNullOrEmpty(token))
|
|
throw new InvalidOperationException($"Pudu login: no token returned. Body: {bodyText}");
|
|
|
|
_token = token;
|
|
}
|
|
|
|
private async Task PostAddCommandAsync(string robotSn, string targetPoint, CancellationToken ct)
|
|
{
|
|
using var req = new HttpRequestMessage(HttpMethod.Post, $"{_baseUrl}/api/Robots/add-command")
|
|
{
|
|
Content = JsonContent.Create(new { Sn = robotSn, TargetPoint = targetPoint })
|
|
};
|
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
|
|
|
|
using var resp = await _http.SendAsync(req, ct);
|
|
resp.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
// Recursive search so we match the token regardless of wrapping (e.g. {"data":{"token":"..."}}).
|
|
// Mirrors the behavior of `sed -n 's/.*"token":"\([^"]*\)".*/\1/p'` in send_robot.sh.
|
|
private static string? ExtractToken(string json)
|
|
{
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(json);
|
|
return FindTokenProperty(doc.RootElement);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string? FindTokenProperty(JsonElement element)
|
|
{
|
|
switch (element.ValueKind)
|
|
{
|
|
case JsonValueKind.Object:
|
|
foreach (var prop in element.EnumerateObject())
|
|
{
|
|
if ((prop.Name.Equals("token", StringComparison.OrdinalIgnoreCase)
|
|
|| prop.Name.Equals("accessToken", StringComparison.OrdinalIgnoreCase))
|
|
&& prop.Value.ValueKind == JsonValueKind.String)
|
|
{
|
|
return prop.Value.GetString();
|
|
}
|
|
}
|
|
foreach (var prop in element.EnumerateObject())
|
|
{
|
|
var nested = FindTokenProperty(prop.Value);
|
|
if (nested is not null) return nested;
|
|
}
|
|
return null;
|
|
|
|
case JsonValueKind.Array:
|
|
foreach (var item in element.EnumerateArray())
|
|
{
|
|
var nested = FindTokenProperty(item);
|
|
if (nested is not null) return nested;
|
|
}
|
|
return null;
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
}
|