using System.Text; using System.Text.Json; namespace AmtScanner.Api.Services; public interface IGuacamoleService { Task GetAuthTokenAsync(); Task CreateOrGetConnectionAsync(string token, string connectionName, string hostname, string username, string password); Task GetConnectionUrlAsync(string token, string connectionId); } public class GuacamoleService : IGuacamoleService { private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly IConfiguration _configuration; private string GuacamoleBaseUrl => _configuration["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole"; private string GuacamoleUsername => _configuration["Guacamole:Username"] ?? "guacadmin"; private string GuacamolePassword => _configuration["Guacamole:Password"] ?? "guacadmin"; public GuacamoleService(HttpClient httpClient, ILogger logger, IConfiguration configuration) { _httpClient = httpClient; _logger = logger; _configuration = configuration; } /// /// 获取 Guacamole 认证 Token /// public async Task GetAuthTokenAsync() { try { var content = new FormUrlEncodedContent(new[] { new KeyValuePair("username", GuacamoleUsername), new KeyValuePair("password", GuacamolePassword) }); var response = await _httpClient.PostAsync($"{GuacamoleBaseUrl}/api/tokens", content); if (!response.IsSuccessStatusCode) { _logger.LogError("Failed to get Guacamole token: {Status}", response.StatusCode); return null; } var json = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(json); if (doc.RootElement.TryGetProperty("authToken", out var tokenElement)) { return tokenElement.GetString(); } return null; } catch (Exception ex) { _logger.LogError(ex, "Error getting Guacamole token"); return null; } } /// /// 创建或获取已存在的连接 /// public async Task CreateOrGetConnectionAsync(string token, string connectionName, string hostname, string username, string password) { try { // 先查找是否已存在同名连接 var existingId = await FindConnectionByNameAsync(token, connectionName); if (existingId != null) { // 更新连接参数 await UpdateConnectionAsync(token, existingId, connectionName, hostname, username, password); return existingId; } // 创建新连接 var connectionData = new Dictionary { ["name"] = connectionName, ["parentIdentifier"] = "ROOT", ["protocol"] = "rdp", ["parameters"] = new Dictionary { ["hostname"] = hostname, ["port"] = "3389", ["username"] = username, ["password"] = password, ["security"] = "any", ["ignore-cert"] = "true", ["resize-method"] = "display-update", ["enable-wallpaper"] = "true", ["enable-theming"] = "true", ["enable-font-smoothing"] = "true", ["enable-full-window-drag"] = "true", ["enable-desktop-composition"] = "true", ["enable-menu-animations"] = "true", ["disable-bitmap-caching"] = "false", ["disable-offscreen-caching"] = "false", ["disable-glyph-caching"] = "false" }, ["attributes"] = new Dictionary() }; var json = JsonSerializer.Serialize(connectionData); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync( $"{GuacamoleBaseUrl}/api/session/data/mysql/connections?token={token}", content); if (!response.IsSuccessStatusCode) { var error = await response.Content.ReadAsStringAsync(); _logger.LogError("Failed to create connection: {Status} - {Error}", response.StatusCode, error); return null; } var responseJson = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(responseJson); if (doc.RootElement.TryGetProperty("identifier", out var idElement)) { return idElement.GetString(); } return null; } catch (Exception ex) { _logger.LogError(ex, "Error creating Guacamole connection"); return null; } } /// /// 查找已存在的连接 /// private async Task FindConnectionByNameAsync(string token, string connectionName) { try { var response = await _httpClient.GetAsync( $"{GuacamoleBaseUrl}/api/session/data/mysql/connections?token={token}"); if (!response.IsSuccessStatusCode) { return null; } var json = await response.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(json); foreach (var prop in doc.RootElement.EnumerateObject()) { if (prop.Value.TryGetProperty("name", out var nameElement) && nameElement.GetString() == connectionName) { return prop.Name; } } return null; } catch (Exception ex) { _logger.LogError(ex, "Error finding connection by name"); return null; } } /// /// 更新连接参数 /// private async Task UpdateConnectionAsync(string token, string connectionId, string connectionName, string hostname, string username, string password) { try { var connectionData = new Dictionary { ["name"] = connectionName, ["parentIdentifier"] = "ROOT", ["protocol"] = "rdp", ["identifier"] = connectionId, ["parameters"] = new Dictionary { ["hostname"] = hostname, ["port"] = "3389", ["username"] = username, ["password"] = password, ["security"] = "any", ["ignore-cert"] = "true", ["resize-method"] = "display-update", ["enable-wallpaper"] = "true", ["enable-theming"] = "true", ["enable-font-smoothing"] = "true", ["enable-full-window-drag"] = "true", ["enable-desktop-composition"] = "true", ["enable-menu-animations"] = "true" }, ["attributes"] = new Dictionary() }; var json = JsonSerializer.Serialize(connectionData); var content = new StringContent(json, Encoding.UTF8, "application/json"); var request = new HttpRequestMessage(HttpMethod.Put, $"{GuacamoleBaseUrl}/api/session/data/mysql/connections/{connectionId}?token={token}") { Content = content }; await _httpClient.SendAsync(request); } catch (Exception ex) { _logger.LogError(ex, "Error updating connection"); } } /// /// 获取连接 URL(用于 iframe 嵌入) /// public async Task GetConnectionUrlAsync(string token, string connectionId) { // Guacamole 客户端 URL 格式 // 连接标识符需要 Base64 编码: connectionId + "\0" + "c" + "\0" + "mysql" var connectionIdentifier = $"{connectionId}\0c\0mysql"; var encodedId = Convert.ToBase64String(Encoding.UTF8.GetBytes(connectionIdentifier)); return $"{GuacamoleBaseUrl}/#/client/{encodedId}?token={token}"; } }