using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Net.Sockets; using System.Text.Json; using NetworkTool.Client.Models; namespace NetworkTool.Client.Services; public sealed class ServerApiService { private readonly JsonSerializerOptions _jsonOptions = new() { PropertyNameCaseInsensitive = true, }; public async Task CheckHealthAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { try { using var handler = new SocketsHttpHandler(); if (!string.IsNullOrWhiteSpace(localIPv4)) { handler.ConnectCallback = async (context, token) => { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Parse(localIPv4), 0)); await socket.ConnectAsync(context.DnsEndPoint, token); return new NetworkStream(socket, ownsSocket: true); }; } using var client = new HttpClient(handler) { BaseAddress = new Uri(baseAddress), Timeout = TimeSpan.FromSeconds(5) }; client.DefaultRequestHeaders.Add("X-Admin-Password", password); using var response = await client.GetAsync("/api/health", cancellationToken); return new HealthCheckResult { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Message = response.IsSuccessStatusCode ? "HTTP 健康检查通过。" : $"HTTP 健康检查返回状态码 {(int)response.StatusCode}。", }; } catch (Exception ex) { return new HealthCheckResult { Success = false, StatusCode = null, Message = ex.Message, }; } } public async Task GetDeviceInfoAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.GetAsync("/api/device/info", cancellationToken); if (!response.IsSuccessStatusCode) { return null; } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return wrapper?.Data; } catch { return null; } } public async Task GetInterfacesAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.GetAsync("/api/network/interfaces", cancellationToken); if (!response.IsSuccessStatusCode) { return null; } await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return wrapper?.Data; } catch { return null; } } public async Task> GetInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.GetAsync("/api/network/configs", cancellationToken); var content = await response.Content.ReadAsStringAsync(cancellationToken); var wrapper = DeserializeEnvelope(content); if (wrapper is null) { return CreateInvalidJsonResult(response.StatusCode, content, "批量读取配置"); } if (wrapper?.Data is null) { return new ApiCallResult { Success = false, StatusCode = (int)response.StatusCode, Message = "接口配置返回内容为空。", }; } return new ApiCallResult { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Message = wrapper.Message, Data = wrapper.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public async Task> ValidateInterfaceConfigAsync(string baseAddress, string password, string localIPv4, RemoteInterfaceConfig input, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/validate", input, _jsonOptions, cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "校验通过" : $"校验失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public Task> ValidateInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, IReadOnlyList inputs, CancellationToken cancellationToken = default) { return ValidateInterfaceConfigsAsync(baseAddress, password, localIPv4, new RemoteInterfaceConfigsRequest { Configs = inputs }, cancellationToken); } private async Task> ValidateInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, RemoteInterfaceConfigsRequest input, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/validate-all", input, _jsonOptions, cancellationToken); var content = await response.Content.ReadAsStringAsync(cancellationToken); var wrapper = DeserializeEnvelope(content); if (wrapper is null) { return CreateInvalidJsonResult(response.StatusCode, content, "批量校验"); } return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "校验通过" : $"校验失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public async Task> ApplyInterfaceConfigAsync(string baseAddress, string password, string localIPv4, RemoteInterfaceConfig input, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/apply", input, _jsonOptions, cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "配置任务已提交" : $"提交失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public Task> ApplyInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, IReadOnlyList inputs, CancellationToken cancellationToken = default) { return ApplyInterfaceConfigsAsync(baseAddress, password, localIPv4, new RemoteInterfaceConfigsRequest { Configs = inputs }, cancellationToken); } private async Task> ApplyInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, RemoteInterfaceConfigsRequest input, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/apply-all", input, _jsonOptions, cancellationToken); var content = await response.Content.ReadAsStringAsync(cancellationToken); var wrapper = DeserializeEnvelope(content); if (wrapper is null) { return CreateInvalidJsonResult(response.StatusCode, content, "批量应用"); } return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "配置任务已提交" : $"提交失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public async Task> GetTaskAsync(string baseAddress, string password, string localIPv4, string taskId, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.GetAsync($"/api/tasks/{Uri.EscapeDataString(taskId)}", cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "成功" : $"任务查询失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public async Task> ConfirmApplyTaskAsync(string baseAddress, string password, string localIPv4, string taskId, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/apply/confirm", new { task_id = taskId }, _jsonOptions, cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "已确认保留配置" : $"确认失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public async Task> CancelApplyTaskAsync(string baseAddress, string password, string localIPv4, string taskId, CancellationToken cancellationToken = default) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsJsonAsync("/api/network/apply/cancel", new { task_id = taskId }, _jsonOptions, cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "已取消保留配置" : $"取消失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } public Task> RebootAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { return PostSystemActionAsync(baseAddress, password, localIPv4, "/api/system/reboot", cancellationToken); } public Task> ShutdownAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default) { return PostSystemActionAsync(baseAddress, password, localIPv4, "/api/system/shutdown", cancellationToken); } private HttpClient CreateClient(string baseAddress, string password, string localIPv4) { var handler = new SocketsHttpHandler(); if (!string.IsNullOrWhiteSpace(localIPv4)) { handler.ConnectCallback = async (context, token) => { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Parse(localIPv4), 0)); await socket.ConnectAsync(context.DnsEndPoint, token); return new NetworkStream(socket, ownsSocket: true); }; } var client = new HttpClient(handler) { BaseAddress = new Uri(baseAddress), Timeout = TimeSpan.FromSeconds(5) }; client.DefaultRequestHeaders.Add("X-Admin-Password", password); return client; } private async Task> PostSystemActionAsync(string baseAddress, string password, string localIPv4, string path, CancellationToken cancellationToken) { try { using var client = CreateClient(baseAddress, password, localIPv4); using var response = await client.PostAsync(path, content: null, cancellationToken); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken); var wrapper = await JsonSerializer.DeserializeAsync>(stream, _jsonOptions, cancellationToken); return new ApiCallResult { Success = response.IsSuccessStatusCode && wrapper?.Data is not null, StatusCode = (int)response.StatusCode, Message = wrapper?.Message ?? (response.IsSuccessStatusCode ? "系统任务已提交" : $"提交失败,HTTP 状态码 {(int)response.StatusCode}。"), Data = wrapper?.Data, }; } catch (Exception ex) { return new ApiCallResult { Success = false, Message = ex.Message, }; } } private sealed class ApiEnvelope { public int Code { get; set; } public string Message { get; set; } = string.Empty; public T? Data { get; set; } } private ApiEnvelope? DeserializeEnvelope(string content) { try { return JsonSerializer.Deserialize>(content, _jsonOptions); } catch (JsonException) { return null; } } private static ApiCallResult CreateInvalidJsonResult(HttpStatusCode statusCode, string content, string actionName) { var body = string.IsNullOrWhiteSpace(content) ? "响应为空" : content.Trim(); if (body.Length > 160) { body = body[..160] + "..."; } var status = (int)statusCode; var hint = status == 404 ? $"Linux 端 Server 可能还未更新,不支持{actionName}接口。请重新发布并启动最新 Server。" : $"Linux 端 Server 返回了无法解析的{actionName}响应。"; return new ApiCallResult { Success = false, StatusCode = status, Message = $"{hint}HTTP {status}:{body}", }; } }