From 398874bfa51bf3be00aa2da4c5a1eacd81b3c3fe Mon Sep 17 00:00:00 2001 From: root <295172551@qq.com> Date: Thu, 26 Jun 2025 23:03:18 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A7=E8=8B=8F=E6=89=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LTEMvcApp/Controllers/HomeController.cs | 87 +++++ LTEMvcApp/Controllers/IpGroupController.cs | 213 +++++++++++++ .../Models/StartCellularNetworkCommand.cs | 13 + .../Models/StopCellularNetworkCommand.cs | 13 + LTEMvcApp/Program.cs | 3 + LTEMvcApp/Services/HttpClientService.cs | 301 ++++++++++++++++++ LTEMvcApp/Views/Home/Index.cshtml | 218 ++++++++++++- 7 files changed, 845 insertions(+), 3 deletions(-) create mode 100644 LTEMvcApp/Controllers/IpGroupController.cs create mode 100644 LTEMvcApp/Models/StartCellularNetworkCommand.cs create mode 100644 LTEMvcApp/Models/StopCellularNetworkCommand.cs create mode 100644 LTEMvcApp/Services/HttpClientService.cs diff --git a/LTEMvcApp/Controllers/HomeController.cs b/LTEMvcApp/Controllers/HomeController.cs index a59668c..edc352f 100644 --- a/LTEMvcApp/Controllers/HomeController.cs +++ b/LTEMvcApp/Controllers/HomeController.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using LTEMvcApp.Models; using LTEMvcApp.Services; +using System.Reflection; namespace LTEMvcApp.Controllers; @@ -31,9 +32,95 @@ public class HomeController : Controller var allTestClients = _webSocketManager.GetAllTestClientsWithState(); ViewBag.Clients = allTestClients; + // 处理IP分组数据 + var ipGroups = ProcessIpGroups(allTestClients); + ViewBag.IpGroups = ipGroups; + return View(); } + /// + /// 处理IP分组数据 + /// + private List ProcessIpGroups(List allTestClients) + { + var ipGroups = new List(); + var ipDict = new Dictionary>(); + + // 按IP地址分组 + foreach (var client in allTestClients) + { + var address = client.Config.Address; + var ip = address.Split(':')[0]; // 根据:分割获取IP + + if (!ipDict.ContainsKey(ip)) + { + ipDict[ip] = new List(); + } + ipDict[ip].Add(client); + } + + // 转换为动态对象列表 + foreach (var kvp in ipDict) + { + var ip = kvp.Key; + var clients = kvp.Value; + + // 计算该IP下客户端的状态 + var connectedCount = clients.Count(c => (LTEMvcApp.Models.ClientState)c.State == LTEMvcApp.Models.ClientState.Connected); + var totalCount = clients.Count; + + var groupState = connectedCount == 0 ? "停止" : + connectedCount == totalCount ? "运行" : "部分运行"; + + // 从TestConfigController获取已保存的Key + var savedKey = GetSavedIpGroupKey(ip); + + ipGroups.Add(new + { + Ip = ip, + Clients = clients, + Port = 11003, + ConnectedCount = connectedCount, + State = groupState, + Key = savedKey + }); + } + + return ipGroups; + } + + /// + /// 获取已保存的IP组Key + /// + /// IP地址 + /// 保存的Key值 + private string GetSavedIpGroupKey(string ip) + { + try + { + // 通过反射获取IpGroupController中的静态字典 + var ipGroupControllerType = typeof(IpGroupController); + var ipGroupKeysField = ipGroupControllerType.GetField("_ipGroupKeys", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); + + if (ipGroupKeysField != null) + { + var ipGroupKeys = ipGroupKeysField.GetValue(null) as Dictionary; + if (ipGroupKeys != null && ipGroupKeys.TryGetValue(ip, out var key)) + { + return key; + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "获取IP组Key失败: {Ip}", ip); + } + + return string.Empty; + } + public IActionResult Privacy() { return View(); diff --git a/LTEMvcApp/Controllers/IpGroupController.cs b/LTEMvcApp/Controllers/IpGroupController.cs new file mode 100644 index 0000000..1bcacbd --- /dev/null +++ b/LTEMvcApp/Controllers/IpGroupController.cs @@ -0,0 +1,213 @@ +using Microsoft.AspNetCore.Mvc; +using LTEMvcApp.Models; +using LTEMvcApp.Services; +using Microsoft.Extensions.Logging; + +namespace LTEMvcApp.Controllers +{ + /// + /// IP组管理控制器 - 负责IP组网络启动、停止和Key管理 + /// + [ApiController] + [Route("api/[controller]")] + public class IpGroupController : ControllerBase + { + private readonly HttpClientService _httpClientService; + private readonly ILogger _logger; + + // 存储IP组的Key配置 + private static readonly Dictionary _ipGroupKeys = new(); + + public IpGroupController(HttpClientService httpClientService, ILogger logger) + { + _httpClientService = httpClientService; + _logger = logger; + } + + /// + /// 保存IP组Key配置 + /// + /// IP组Key请求 + /// 操作结果 + [HttpPost("key")] + public ActionResult SaveIpGroupKey([FromBody] IpGroupKeyRequest request) + { + if (string.IsNullOrEmpty(request.Ip)) + return BadRequest("IP地址不能为空"); + + _ipGroupKeys[request.Ip] = request.Key ?? string.Empty; + _logger.LogInformation("保存IP组Key: {Ip} -> {Key}", request.Ip, request.Key); + + return Ok(new { message = "IP组Key保存成功" }); + } + + /// + /// 启动IP组网络 + /// + /// 启动网络请求 + /// 操作结果 + [HttpPost("start")] + public async Task StartIpGroup([FromBody] StartIpGroupRequest request) + { + if (string.IsNullOrEmpty(request.Ip)) + return BadRequest("IP地址不能为空"); + + if (string.IsNullOrEmpty(request.Port)) + return BadRequest("端口不能为空"); + + // 检查Key是否已配置 + if (!_ipGroupKeys.TryGetValue(request.Ip, out var key) || string.IsNullOrEmpty(key)) + return BadRequest("请先配置网络Key"); + + try + { + var apiUrl = $"http://{request.Ip}:{request.Port}/api/v1/CellularNetwork/start"; + var command = new StartCellularNetworkCommand { Key = key }; + + _logger.LogInformation("启动IP组网络: {Ip}:{Port}, Key: {Key}", request.Ip, request.Port, key); + + var response = await _httpClientService.PostJsonAsync(apiUrl, command); + + _logger.LogInformation("IP组网络启动成功: {Response}", response); + return Ok(new { message = "网络启动成功", response }); + } + catch (Exception ex) + { + _logger.LogError(ex, "启动IP组网络失败: {Ip}:{Port}", request.Ip, request.Port); + return BadRequest($"启动网络失败: {ex.Message}"); + } + } + + /// + /// 停止IP组网络 + /// + /// 停止网络请求 + /// 操作结果 + [HttpPost("stop")] + public async Task StopIpGroup([FromBody] StopIpGroupRequest request) + { + if (string.IsNullOrEmpty(request.Ip)) + return BadRequest("IP地址不能为空"); + + if (string.IsNullOrEmpty(request.Port)) + return BadRequest("端口不能为空"); + + // 检查Key是否已配置 + if (!_ipGroupKeys.TryGetValue(request.Ip, out var key) || string.IsNullOrEmpty(key)) + return BadRequest("请先配置网络Key"); + + try + { + var apiUrl = $"http://{request.Ip}:{request.Port}/api/v1/CellularNetwork/stop"; + var command = new StopCellularNetworkCommand { Key = key }; + + _logger.LogInformation("停止IP组网络: {Ip}:{Port}, Key: {Key}", request.Ip, request.Port, key); + + var response = await _httpClientService.PostJsonAsync(apiUrl, command); + + _logger.LogInformation("IP组网络停止成功: {Response}", response); + return Ok(new { message = "网络停止成功", response }); + } + catch (Exception ex) + { + _logger.LogError(ex, "停止IP组网络失败: {Ip}:{Port}", request.Ip, request.Port); + return BadRequest($"停止网络失败: {ex.Message}"); + } + } + + /// + /// 获取IP组Key配置 + /// + /// IP地址 + /// Key配置 + [HttpGet("key/{ip}")] + public ActionResult GetIpGroupKey(string ip) + { + if (string.IsNullOrEmpty(ip)) + return BadRequest("IP地址不能为空"); + + var key = _ipGroupKeys.TryGetValue(ip, out var value) ? value : string.Empty; + return Ok(new { ip, key }); + } + + /// + /// 获取所有IP组Key配置 + /// + /// 所有IP组Key配置 + [HttpGet("keys")] + public ActionResult GetAllIpGroupKeys() + { + return Ok(_ipGroupKeys); + } + + /// + /// 删除IP组Key配置 + /// + /// IP地址 + /// 操作结果 + [HttpDelete("key/{ip}")] + public ActionResult DeleteIpGroupKey(string ip) + { + if (string.IsNullOrEmpty(ip)) + return BadRequest("IP地址不能为空"); + + if (_ipGroupKeys.Remove(ip)) + { + _logger.LogInformation("删除IP组Key: {Ip}", ip); + return Ok(new { message = "IP组Key删除成功" }); + } + else + { + return NotFound($"未找到IP地址为 {ip} 的Key配置"); + } + } + } + + /// + /// IP组Key请求 + /// + public class IpGroupKeyRequest + { + /// + /// IP地址 + /// + public string Ip { get; set; } = string.Empty; + + /// + /// Key值 + /// + public string? Key { get; set; } + } + + /// + /// 启动IP组请求 + /// + public class StartIpGroupRequest + { + /// + /// IP地址 + /// + public string Ip { get; set; } = string.Empty; + + /// + /// 端口 + /// + public string Port { get; set; } = string.Empty; + } + + /// + /// 停止IP组请求 + /// + public class StopIpGroupRequest + { + /// + /// IP地址 + /// + public string Ip { get; set; } = string.Empty; + + /// + /// 端口 + /// + public string Port { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/LTEMvcApp/Models/StartCellularNetworkCommand.cs b/LTEMvcApp/Models/StartCellularNetworkCommand.cs new file mode 100644 index 0000000..26ee027 --- /dev/null +++ b/LTEMvcApp/Models/StartCellularNetworkCommand.cs @@ -0,0 +1,13 @@ +namespace LTEMvcApp.Models +{ + /// + /// 启动蜂窝网络命令 + /// + public class StartCellularNetworkCommand + { + /// + /// 网络配置键 + /// + public string Key { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/LTEMvcApp/Models/StopCellularNetworkCommand.cs b/LTEMvcApp/Models/StopCellularNetworkCommand.cs new file mode 100644 index 0000000..1e2bf8d --- /dev/null +++ b/LTEMvcApp/Models/StopCellularNetworkCommand.cs @@ -0,0 +1,13 @@ +namespace LTEMvcApp.Models +{ + /// + /// 停止蜂窝网络命令 + /// + public class StopCellularNetworkCommand + { + /// + /// 网络接口名称 + /// + public string Key { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/LTEMvcApp/Program.cs b/LTEMvcApp/Program.cs index 21c7f0b..e5be280 100644 --- a/LTEMvcApp/Program.cs +++ b/LTEMvcApp/Program.cs @@ -11,6 +11,9 @@ builder.WebHost.ConfigureKestrel(serverOptions => // Add services to the container. builder.Services.AddControllersWithViews(); +// 注册HttpClient服务 +builder.Services.AddHttpClient(); + // 注册WebSocket管理服务 builder.Services.AddSingleton(sp => new WebSocketManagerService( diff --git a/LTEMvcApp/Services/HttpClientService.cs b/LTEMvcApp/Services/HttpClientService.cs new file mode 100644 index 0000000..32a26ae --- /dev/null +++ b/LTEMvcApp/Services/HttpClientService.cs @@ -0,0 +1,301 @@ +using System; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace LTEMvcApp.Services +{ + /// + /// HTTP客户端服务 - 提供HTTP请求功能 + /// + public class HttpClientService + { + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + + public HttpClientService(HttpClient httpClient, ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + + // 设置默认请求头 + _httpClient.DefaultRequestHeaders.Add("User-Agent", "LTEMvcApp/1.0"); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + + _logger.LogInformation("HttpClientService 初始化完成"); + } + + #region GET请求方法 + + /// + /// 发送GET请求 + /// + /// 请求URL + /// 自定义请求头 + /// 响应内容 + public async Task GetAsync(string url, Dictionary? headers = null) + { + try + { + _logger.LogInformation("发送GET请求到: {Url}", url); + + using var request = new HttpRequestMessage(HttpMethod.Get, url); + + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("GET请求成功,响应长度: {Length}", content.Length); + + return content; + } + catch (Exception ex) + { + _logger.LogError(ex, "GET请求失败: {Url}", url); + throw; + } + } + + /// + /// 发送GET请求并反序列化为指定类型 + /// + /// 目标类型 + /// 请求URL + /// 自定义请求头 + /// 反序列化的对象 + public async Task GetAsync(string url, Dictionary? headers = null) + { + var json = await GetAsync(url, headers); + return JsonConvert.DeserializeObject(json) ?? throw new InvalidOperationException("反序列化失败"); + } + + #endregion + + #region POST请求方法 + + /// + /// 发送POST请求 + /// + /// 请求URL + /// 请求内容 + /// 内容类型 + /// 自定义请求头 + /// 响应内容 + public async Task PostAsync(string url, string content, string contentType = "application/json", Dictionary? headers = null) + { + try + { + _logger.LogInformation("发送POST请求到: {Url}", url); + + using var request = new HttpRequestMessage(HttpMethod.Post, url); + + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + request.Content = new StringContent(content, Encoding.UTF8, contentType); + + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("POST请求成功,响应长度: {Length}", responseContent.Length); + + return responseContent; + } + catch (Exception ex) + { + _logger.LogError(ex, "POST请求失败: {Url}", url); + throw; + } + } + + /// + /// 发送POST请求(JSON格式) + /// + /// 请求URL + /// 要发送的数据对象 + /// 自定义请求头 + /// 响应内容 + public async Task PostJsonAsync(string url, object data, Dictionary? headers = null) + { + var json = JsonConvert.SerializeObject(data); + return await PostAsync(url, json, "application/json", headers); + } + + /// + /// 发送POST请求并反序列化响应 + /// + /// 响应类型 + /// 请求URL + /// 要发送的数据对象 + /// 自定义请求头 + /// 反序列化的响应对象 + public async Task PostJsonAsync(string url, object data, Dictionary? headers = null) + { + var json = await PostJsonAsync(url, data, headers); + return JsonConvert.DeserializeObject(json) ?? throw new InvalidOperationException("反序列化失败"); + } + + #endregion + + #region PUT请求方法 + + /// + /// 发送PUT请求 + /// + /// 请求URL + /// 请求内容 + /// 内容类型 + /// 自定义请求头 + /// 响应内容 + public async Task PutAsync(string url, string content, string contentType = "application/json", Dictionary? headers = null) + { + try + { + _logger.LogInformation("发送PUT请求到: {Url}", url); + + using var request = new HttpRequestMessage(HttpMethod.Put, url); + + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + request.Content = new StringContent(content, Encoding.UTF8, contentType); + + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var responseContent = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("PUT请求成功,响应长度: {Length}", responseContent.Length); + + return responseContent; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "PUT请求失败: {Url}", url); + throw; + } + catch (TaskCanceledException ex) + { + _logger.LogError(ex, "PUT请求超时: {Url}", url); + throw; + } + } + + /// + /// 发送PUT请求(JSON格式) + /// + /// 请求URL + /// 要发送的数据对象 + /// 自定义请求头 + /// 响应内容 + public async Task PutJsonAsync(string url, object data, Dictionary? headers = null) + { + var json = JsonConvert.SerializeObject(data); + return await PutAsync(url, json, "application/json", headers); + } + + #endregion + + #region DELETE请求方法 + + /// + /// 发送DELETE请求 + /// + /// 请求URL + /// 自定义请求头 + /// 响应内容 + public async Task DeleteAsync(string url, Dictionary? headers = null) + { + try + { + _logger.LogInformation("发送DELETE请求到: {Url}", url); + + using var request = new HttpRequestMessage(HttpMethod.Delete, url); + + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + _logger.LogInformation("DELETE请求成功,响应长度: {Length}", content.Length); + + return content; + } + catch (HttpRequestException ex) + { + _logger.LogError(ex, "DELETE请求失败: {Url}", url); + throw; + } + catch (TaskCanceledException ex) + { + _logger.LogError(ex, "DELETE请求超时: {Url}", url); + throw; + } + } + + #endregion + + #region 工具方法 + + /// + /// 设置默认请求头 + /// + /// 头部名称 + /// 头部值 + public void SetDefaultHeader(string name, string value) + { + _httpClient.DefaultRequestHeaders.Remove(name); + _httpClient.DefaultRequestHeaders.Add(name, value); + _logger.LogInformation("设置默认请求头: {Name} = {Value}", name, value); + } + + /// + /// 设置超时时间 + /// + /// 超时时间 + public void SetTimeout(TimeSpan timeout) + { + _httpClient.Timeout = timeout; + _logger.LogInformation("设置超时时间: {Timeout}", timeout); + } + + /// + /// 获取当前HttpClient实例 + /// + /// HttpClient实例 + public HttpClient GetHttpClient() + { + return _httpClient; + } + + #endregion + } +} \ No newline at end of file diff --git a/LTEMvcApp/Views/Home/Index.cshtml b/LTEMvcApp/Views/Home/Index.cshtml index 214b6cb..92105e5 100644 --- a/LTEMvcApp/Views/Home/Index.cshtml +++ b/LTEMvcApp/Views/Home/Index.cshtml @@ -1,6 +1,7 @@ @{ ViewData["Title"] = "主页"; var clients = ViewBag.Clients as List; + var ipGroups = ViewBag.IpGroups as List; }
+
+
+
+
+

+ AgentService +

+
+
+
+ + + + + + + + + + + + @if (ipGroups != null) + { + foreach (var group in ipGroups) + { + + + + + + + + } + } + +
AgentIPAgentPortKey状态操作
@group.Ip@group.Port + + + @if (group.State == "运行") + { + 运行 + } + else if (group.State == "部分运行") + { + 部分运行 + } + else + { + 停止 + } + + + 启动网络 + + + 停止网络 + +
+
+
+
+
+
+ +
@@ -167,15 +267,21 @@ @if (state == LTEMvcApp.Models.ClientState.Connected) { - 运行 + + + 运行 } else if (state == LTEMvcApp.Models.ClientState.Stop) { - 未启动 + + + 未启动 } else { - 停止 + + + 停止 } @@ -275,5 +381,111 @@ } }); } + + function updateGroupKey(ip, key) { + // 发送AJAX请求保存到服务器 + $.ajax({ + url: '/api/ipgroup/key', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ ip: ip, key: key }), + success: function(response) { + console.log('IP组Key保存成功:', response); + // 可以显示一个小的成功提示 + showToast('Key保存成功', 'success'); + }, + error: function(xhr) { + console.error('IP组Key保存失败:', xhr.responseText); + showToast('Key保存失败', 'error'); + } + }); + } + + function startIpGroup(ip) { + // 获取当前行的端口信息 + var row = $('tr').filter(function() { + return $(this).find('td:first').text().trim() === ip; + }); + + var port = row.find('td:eq(1)').text().trim(); // 第二列是端口 + var keyInput = row.find('input[type="text"]'); + var key = keyInput.val().trim(); + + if (!key) { + alert('请先填写网络Key!'); + keyInput.focus(); + return; + } + + if (!confirm('确定要启动该网络吗?')) return; + + $.ajax({ + url: '/api/ipgroup/start', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ ip: ip, port: port }), + success: function(response) { + showToast('网络启动成功', 'success'); + setTimeout(() => location.reload(), 2000); + }, + error: function(xhr) { + var errorMsg = '启动失败'; + try { + var errorResponse = JSON.parse(xhr.responseText); + errorMsg = errorResponse.message || errorResponse; + } catch (e) { + errorMsg = xhr.responseText || '启动失败'; + } + showToast(errorMsg, 'error'); + } + }); + } + + function stopIpGroup(ip) { + // 获取当前行的端口信息 + var row = $('tr').filter(function() { + return $(this).find('td:first').text().trim() === ip; + }); + + var port = row.find('td:eq(1)').text().trim(); // 第二列是端口 + + if (!confirm('确定要停止该网络吗?')) return; + + $.ajax({ + url: '/api/ipgroup/stop', + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({ ip: ip, port: port }), + success: function(response) { + showToast('网络停止成功', 'success'); + setTimeout(() => location.reload(), 2000); + }, + error: function(xhr) { + var errorMsg = '停止失败'; + try { + var errorResponse = JSON.parse(xhr.responseText); + errorMsg = errorResponse.message || errorResponse; + } catch (e) { + errorMsg = xhr.responseText || '停止失败'; + } + showToast(errorMsg, 'error'); + } + }); + } + + function showToast(message, type) { + // 简单的提示函数,可以替换为更美观的toast组件 + var alertClass = type === 'success' ? 'alert-success' : 'alert-danger'; + var alertHtml = ''; + $('body').append(alertHtml); + + // 3秒后自动移除 + setTimeout(function() { + $('.alert').fadeOut(); + }, 3000); + } }