296 lines
10 KiB
C#
296 lines
10 KiB
C#
using System.Net.Http.Headers;
|
||
using System.Text;
|
||
using System.Text.Json;
|
||
|
||
namespace AmtScanner.Api.Services;
|
||
|
||
/// <summary>
|
||
/// Guacamole 远程桌面服务
|
||
/// </summary>
|
||
public class GuacamoleService
|
||
{
|
||
private readonly ILogger<GuacamoleService> _logger;
|
||
private readonly HttpClient _httpClient;
|
||
private readonly IConfiguration _config;
|
||
|
||
private string? _authToken;
|
||
private DateTime _tokenExpiry = DateTime.MinValue;
|
||
|
||
public GuacamoleService(ILogger<GuacamoleService> logger, IHttpClientFactory httpClientFactory, IConfiguration config)
|
||
{
|
||
_logger = logger;
|
||
_httpClient = httpClientFactory.CreateClient("Guacamole");
|
||
_config = config;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取 Guacamole 认证 Token
|
||
/// </summary>
|
||
public async Task<string?> GetAuthTokenAsync()
|
||
{
|
||
// 如果 token 还有效,直接返回
|
||
if (!string.IsNullOrEmpty(_authToken) && DateTime.Now < _tokenExpiry)
|
||
{
|
||
return _authToken;
|
||
}
|
||
|
||
try
|
||
{
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var username = _config["Guacamole:Username"] ?? "guacadmin";
|
||
var password = _config["Guacamole:Password"] ?? "guacadmin";
|
||
|
||
var content = new FormUrlEncodedContent(new[]
|
||
{
|
||
new KeyValuePair<string, string>("username", username),
|
||
new KeyValuePair<string, string>("password", password)
|
||
});
|
||
|
||
var response = await _httpClient.PostAsync($"{baseUrl}/api/tokens", content);
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
var json = await response.Content.ReadAsStringAsync();
|
||
var result = JsonSerializer.Deserialize<JsonElement>(json);
|
||
|
||
_authToken = result.GetProperty("authToken").GetString();
|
||
_tokenExpiry = DateTime.Now.AddMinutes(30); // Token 有效期约 30 分钟
|
||
|
||
_logger.LogInformation("Guacamole 认证成功");
|
||
return _authToken;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogError("Guacamole 认证失败: {Status}", response.StatusCode);
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "获取 Guacamole Token 失败");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建 RDP 连接
|
||
/// </summary>
|
||
public async Task<string?> CreateRdpConnectionAsync(string name, string hostname, string username, string password, int port = 3389)
|
||
{
|
||
var token = await GetAuthTokenAsync();
|
||
if (string.IsNullOrEmpty(token)) return null;
|
||
|
||
try
|
||
{
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
|
||
|
||
var connection = new
|
||
{
|
||
parentIdentifier = "ROOT",
|
||
name = name,
|
||
protocol = "rdp",
|
||
parameters = new
|
||
{
|
||
hostname = hostname,
|
||
port = port.ToString(),
|
||
username = username,
|
||
password = password,
|
||
security = "any",
|
||
ignoreCert = "true",
|
||
enableDrive = "true",
|
||
createDrivePath = "true"
|
||
},
|
||
attributes = new
|
||
{
|
||
maxConnections = "",
|
||
maxConnectionsPerUser = ""
|
||
}
|
||
};
|
||
|
||
var json = JsonSerializer.Serialize(connection);
|
||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||
|
||
var response = await _httpClient.PostAsync(
|
||
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}",
|
||
content);
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
var resultJson = await response.Content.ReadAsStringAsync();
|
||
var result = JsonSerializer.Deserialize<JsonElement>(resultJson);
|
||
var connectionId = result.GetProperty("identifier").GetString();
|
||
|
||
_logger.LogInformation("创建 RDP 连接成功: {Id}", connectionId);
|
||
return connectionId;
|
||
}
|
||
else
|
||
{
|
||
var error = await response.Content.ReadAsStringAsync();
|
||
_logger.LogError("创建 RDP 连接失败: {Status} - {Error}", response.StatusCode, error);
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "创建 RDP 连接异常");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建 VNC 连接
|
||
/// </summary>
|
||
public async Task<string?> CreateVncConnectionAsync(string name, string hostname, string password, int port = 5900)
|
||
{
|
||
var token = await GetAuthTokenAsync();
|
||
if (string.IsNullOrEmpty(token)) return null;
|
||
|
||
try
|
||
{
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
|
||
|
||
var connection = new
|
||
{
|
||
parentIdentifier = "ROOT",
|
||
name = name,
|
||
protocol = "vnc",
|
||
parameters = new
|
||
{
|
||
hostname = hostname,
|
||
port = port.ToString(),
|
||
password = password
|
||
},
|
||
attributes = new
|
||
{
|
||
maxConnections = "",
|
||
maxConnectionsPerUser = ""
|
||
}
|
||
};
|
||
|
||
var json = JsonSerializer.Serialize(connection);
|
||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||
|
||
var response = await _httpClient.PostAsync(
|
||
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}",
|
||
content);
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
var resultJson = await response.Content.ReadAsStringAsync();
|
||
var result = JsonSerializer.Deserialize<JsonElement>(resultJson);
|
||
var connectionId = result.GetProperty("identifier").GetString();
|
||
|
||
_logger.LogInformation("创建 VNC 连接成功: {Id}", connectionId);
|
||
return connectionId;
|
||
}
|
||
else
|
||
{
|
||
var error = await response.Content.ReadAsStringAsync();
|
||
_logger.LogError("创建 VNC 连接失败: {Status} - {Error}", response.StatusCode, error);
|
||
return null;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "创建 VNC 连接异常");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除连接
|
||
/// </summary>
|
||
public async Task<bool> DeleteConnectionAsync(string connectionId)
|
||
{
|
||
var token = await GetAuthTokenAsync();
|
||
if (string.IsNullOrEmpty(token)) return false;
|
||
|
||
try
|
||
{
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
|
||
|
||
var response = await _httpClient.DeleteAsync(
|
||
$"{baseUrl}/api/session/data/{dataSource}/connections/{connectionId}?token={token}");
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
_logger.LogInformation("删除连接成功: {Id}", connectionId);
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
_logger.LogError("删除连接失败: {Status}", response.StatusCode);
|
||
return false;
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "删除连接异常");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取连接 URL(用于 iframe 嵌入)
|
||
/// </summary>
|
||
public async Task<string?> GetConnectionUrlAsync(string connectionId)
|
||
{
|
||
var token = await GetAuthTokenAsync();
|
||
if (string.IsNullOrEmpty(token)) return null;
|
||
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
|
||
|
||
// 构建客户端连接 URL
|
||
// 格式: BASE_URL/#/client/BASE64(connectionId + '\0' + 'c' + '\0' + dataSource)
|
||
var clientId = $"{connectionId}\0c\0{dataSource}";
|
||
var encodedClientId = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientId));
|
||
|
||
return $"{baseUrl}/#/client/{encodedClientId}?token={token}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取所有连接
|
||
/// </summary>
|
||
public async Task<List<GuacamoleConnection>> GetConnectionsAsync()
|
||
{
|
||
var token = await GetAuthTokenAsync();
|
||
if (string.IsNullOrEmpty(token)) return new List<GuacamoleConnection>();
|
||
|
||
try
|
||
{
|
||
var baseUrl = _config["Guacamole:BaseUrl"] ?? "http://localhost:8080/guacamole";
|
||
var dataSource = _config["Guacamole:DataSource"] ?? "mysql";
|
||
|
||
var response = await _httpClient.GetAsync(
|
||
$"{baseUrl}/api/session/data/{dataSource}/connections?token={token}");
|
||
|
||
if (response.IsSuccessStatusCode)
|
||
{
|
||
var json = await response.Content.ReadAsStringAsync();
|
||
var connections = JsonSerializer.Deserialize<Dictionary<string, GuacamoleConnection>>(json,
|
||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||
|
||
return connections?.Values.ToList() ?? new List<GuacamoleConnection>();
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "获取连接列表失败");
|
||
}
|
||
|
||
return new List<GuacamoleConnection>();
|
||
}
|
||
}
|
||
|
||
public class GuacamoleConnection
|
||
{
|
||
public string Identifier { get; set; } = "";
|
||
public string Name { get; set; } = "";
|
||
public string Protocol { get; set; } = "";
|
||
public string ParentIdentifier { get; set; } = "";
|
||
}
|