using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace LicenseSdk;
///
/// License System V5 授权服务 .NET 客户端。
/// 封装后台 /api/card/* 接口:登录验证、心跳、设备列表、解绑。
/// 提供两套等价方法:
/// - LoginAsync / HeartbeatAsync / ListDevicesAsync / UnbindAsync:返回 JSON 文档,非 2xx 抛异常(兼容旧版);
/// - LoginResponseAsync 等带 ResponseAsync 后缀方法:返回 HTTP 状态码 + 原始响应体(便于展示服务端业务错误)。
///
public sealed class LicenseClient
{
private readonly HttpClient http;
private readonly string machineCode;
private static readonly JsonSerializerOptions JsonOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
public LicenseClient(HttpClient http, string machineCode)
{
this.http = http;
this.machineCode = machineCode.Trim().ToUpperInvariant();
}
public Task LoginAsync(string cardKey, string? deviceName = null) =>
PostAsync("/api/card/verify", new { cardKey, machineCode, deviceName });
public Task HeartbeatAsync(string cardKey) =>
PostAsync("/api/card/heartbeat", new { cardKey, machineCode });
public Task ListDevicesAsync(string cardKey) =>
PostAsync("/api/card/devices", new { cardKey, machineCode });
public Task UnbindAsync(string cardKey) =>
PostAsync("/api/card/unbind", new { cardKey, machineCode });
public Task LoginResponseAsync(string cardKey, string? deviceName = null) =>
PostResponseAsync("/api/card/verify", new { cardKey, machineCode, deviceName });
public Task HeartbeatResponseAsync(string cardKey) =>
PostResponseAsync("/api/card/heartbeat", new { cardKey, machineCode });
public Task ListDevicesResponseAsync(string cardKey) =>
PostResponseAsync("/api/card/devices", new { cardKey, machineCode });
public Task UnbindResponseAsync(string cardKey) =>
PostResponseAsync("/api/card/unbind", new { cardKey, machineCode });
private async Task PostAsync(string path, object payload)
{
using var response = await http.PostAsJsonAsync(path, payload, JsonOptions);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync();
}
private async Task PostResponseAsync(string path, object payload)
{
using var response = await http.PostAsJsonAsync(path, payload, JsonOptions);
string raw = await response.Content.ReadAsStringAsync();
JsonDocument? doc = null;
if (!string.IsNullOrWhiteSpace(raw))
{
try { doc = JsonDocument.Parse(raw); }
catch (JsonException) { /* 非 JSON 响应,保留原始文本 */ }
}
return new ApiResponse(response.StatusCode, raw, doc);
}
}
/// 非 2xx 不抛异常的响应包装:HTTP 状态码 + 原始响应体 + 尽力解析的 JSON 文档。
public sealed class ApiResponse
{
public HttpStatusCode StatusCode { get; }
public string RawBody { get; }
public JsonDocument? Doc { get; }
public ApiResponse(HttpStatusCode statusCode, string rawBody, JsonDocument? doc)
{
StatusCode = statusCode;
RawBody = rawBody;
Doc = doc;
}
public bool IsSuccess => (int)StatusCode is >= 200 and < 300;
public bool OkFlag => Doc is { } d &&
d.RootElement.TryGetProperty("ok", out var ok) &&
ok.ValueKind == JsonValueKind.True;
}