| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- 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<HealthCheckResult> 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<RemoteDeviceInfo?> 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<ApiEnvelope<RemoteDeviceInfo>>(stream, _jsonOptions, cancellationToken);
- return wrapper?.Data;
- }
- catch
- {
- return null;
- }
- }
- public async Task<RemoteInterfacesInfo?> 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<ApiEnvelope<RemoteInterfacesInfo>>(stream, _jsonOptions, cancellationToken);
- return wrapper?.Data;
- }
- catch
- {
- return null;
- }
- }
- public async Task<ApiCallResult<RemoteInterfaceConfigsResponse>> 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<RemoteInterfaceConfigsResponse>(content);
- if (wrapper is null)
- {
- return CreateInvalidJsonResult<RemoteInterfaceConfigsResponse>(response.StatusCode, content, "批量读取配置");
- }
- if (wrapper?.Data is null)
- {
- return new ApiCallResult<RemoteInterfaceConfigsResponse>
- {
- Success = false,
- StatusCode = (int)response.StatusCode,
- Message = "接口配置返回内容为空。",
- };
- }
- return new ApiCallResult<RemoteInterfaceConfigsResponse>
- {
- Success = response.IsSuccessStatusCode,
- StatusCode = (int)response.StatusCode,
- Message = wrapper.Message,
- Data = wrapper.Data,
- };
- }
- catch (Exception ex)
- {
- return new ApiCallResult<RemoteInterfaceConfigsResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public async Task<ApiCallResult<RemoteValidateResult>> 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<ApiEnvelope<RemoteValidateResult>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteValidateResult>
- {
- 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<RemoteValidateResult>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public Task<ApiCallResult<RemoteValidateResult>> ValidateInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, IReadOnlyList<RemoteInterfaceConfig> inputs, CancellationToken cancellationToken = default)
- {
- return ValidateInterfaceConfigsAsync(baseAddress, password, localIPv4, new RemoteInterfaceConfigsRequest { Configs = inputs }, cancellationToken);
- }
- private async Task<ApiCallResult<RemoteValidateResult>> 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<RemoteValidateResult>(content);
- if (wrapper is null)
- {
- return CreateInvalidJsonResult<RemoteValidateResult>(response.StatusCode, content, "批量校验");
- }
- return new ApiCallResult<RemoteValidateResult>
- {
- 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<RemoteValidateResult>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public async Task<ApiCallResult<RemoteApplyTaskResponse>> 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<ApiEnvelope<RemoteApplyTaskResponse>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteApplyTaskResponse>
- {
- 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<RemoteApplyTaskResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public Task<ApiCallResult<RemoteApplyTaskResponse>> ApplyInterfaceConfigsAsync(string baseAddress, string password, string localIPv4, IReadOnlyList<RemoteInterfaceConfig> inputs, CancellationToken cancellationToken = default)
- {
- return ApplyInterfaceConfigsAsync(baseAddress, password, localIPv4, new RemoteInterfaceConfigsRequest { Configs = inputs }, cancellationToken);
- }
- private async Task<ApiCallResult<RemoteApplyTaskResponse>> 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<RemoteApplyTaskResponse>(content);
- if (wrapper is null)
- {
- return CreateInvalidJsonResult<RemoteApplyTaskResponse>(response.StatusCode, content, "批量应用");
- }
- return new ApiCallResult<RemoteApplyTaskResponse>
- {
- 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<RemoteApplyTaskResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public async Task<ApiCallResult<RemoteTaskResult>> 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<ApiEnvelope<RemoteTaskResult>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteTaskResult>
- {
- 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<RemoteTaskResult>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public async Task<ApiCallResult<RemoteApplyTaskResponse>> 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<ApiEnvelope<RemoteApplyTaskResponse>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteApplyTaskResponse>
- {
- 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<RemoteApplyTaskResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public async Task<ApiCallResult<RemoteApplyTaskResponse>> 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<ApiEnvelope<RemoteApplyTaskResponse>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteApplyTaskResponse>
- {
- 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<RemoteApplyTaskResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- public Task<ApiCallResult<RemoteSystemActionResponse>> RebootAsync(string baseAddress, string password, string localIPv4, CancellationToken cancellationToken = default)
- {
- return PostSystemActionAsync(baseAddress, password, localIPv4, "/api/system/reboot", cancellationToken);
- }
- public Task<ApiCallResult<RemoteSystemActionResponse>> 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<ApiCallResult<RemoteSystemActionResponse>> 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<ApiEnvelope<RemoteSystemActionResponse>>(stream, _jsonOptions, cancellationToken);
- return new ApiCallResult<RemoteSystemActionResponse>
- {
- 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<RemoteSystemActionResponse>
- {
- Success = false,
- Message = ex.Message,
- };
- }
- }
- private sealed class ApiEnvelope<T>
- {
- public int Code { get; set; }
- public string Message { get; set; } = string.Empty;
- public T? Data { get; set; }
- }
- private ApiEnvelope<T>? DeserializeEnvelope<T>(string content)
- {
- try
- {
- return JsonSerializer.Deserialize<ApiEnvelope<T>>(content, _jsonOptions);
- }
- catch (JsonException)
- {
- return null;
- }
- }
- private static ApiCallResult<T> CreateInvalidJsonResult<T>(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<T>
- {
- Success = false,
- StatusCode = status,
- Message = $"{hint}HTTP {status}:{body}",
- };
- }
- }
|