Phase1_Day3_OwlAdapter
This commit is contained in:
@@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Host", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Core", "src\IntegrationGateway.Core\IntegrationGateway.Core.csproj", "{2055B7A5-418F-456C-8642-A99A68282561}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Core", "src\IntegrationGateway.Core\IntegrationGateway.Core.csproj", "{2055B7A5-418F-456C-8642-A99A68282561}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationGateway.Adapters.Owl", "src\IntegrationGateway.Adapters.Owl\IntegrationGateway.Adapters.Owl.csproj", "{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -43,6 +45,18 @@ Global
|
|||||||
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x64.Build.0 = Release|Any CPU
|
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x86.ActiveCfg = Release|Any CPU
|
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x86.Build.0 = Release|Any CPU
|
{2055B7A5-418F-456C-8642-A99A68282561}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -50,5 +64,6 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{387BD4FC-725B-4948-B413-F50BC6BD605D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{387BD4FC-725B-4948-B413-F50BC6BD605D} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
{2055B7A5-418F-456C-8642-A99A68282561} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
{2055B7A5-418F-456C-8642-A99A68282561} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
{8B0BD376-E1C5-4B62-AEC9-E2F4397C991E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\IntegrationGateway.Core\IntegrationGateway.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
324
gateway/src/IntegrationGateway.Adapters.Owl/OwlAdapter.cs
Normal file
324
gateway/src/IntegrationGateway.Adapters.Owl/OwlAdapter.cs
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
using IntegrationGateway.Core.Abstractions;
|
||||||
|
using IntegrationGateway.Core.Infrastructure;
|
||||||
|
using IntegrationGateway.Core.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace IntegrationGateway.Adapters.Owl;
|
||||||
|
|
||||||
|
public class OwlAdapter : IHasFlatDevices, IHasStreams, IAcceptsMetadataPush
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
private readonly OwlAuthHelper _auth;
|
||||||
|
private readonly RateLimiter _limiter = new(5);
|
||||||
|
|
||||||
|
public string AdapterCode { get; }
|
||||||
|
public string DisplayName => $"Owl ({AdapterCode})";
|
||||||
|
public AdapterCapabilities Capabilities => new()
|
||||||
|
{
|
||||||
|
HasFlatDevices = true, HasStreams = true,
|
||||||
|
HasPtz = true, HasRecordings = true,
|
||||||
|
AcceptsMetadataPush = true
|
||||||
|
};
|
||||||
|
|
||||||
|
public OwlAdapter(string adapterCode, HttpClient http, string baseUrl, string username, string password)
|
||||||
|
{
|
||||||
|
AdapterCode = adapterCode;
|
||||||
|
_http = http;
|
||||||
|
_auth = new OwlAuthHelper(http, baseUrl, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
await _auth.GetTokenAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> HealthCheckAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var resp = await client.GetAsync("/health");
|
||||||
|
return resp.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── IHasFlatDevices ───
|
||||||
|
public async Task<PagedResult<StandardDevice>> GetDevicesAsync(int page, int size, string? keyword = null)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var url = $"/devices?page={page}&size={size}";
|
||||||
|
if (!string.IsNullOrEmpty(keyword)) url += $"&key={Uri.EscapeDataString(keyword)}";
|
||||||
|
var json = await client.GetStringAsync(url);
|
||||||
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlDevice>>(json)!;
|
||||||
|
return new PagedResult<StandardDevice>
|
||||||
|
{
|
||||||
|
Items = owl.Items.Select(MapDevice).ToList(),
|
||||||
|
Total = owl.Total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StandardDevice?> GetDeviceAsync(string sourceDeviceId)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var json = await client.GetStringAsync($"/devices/{sourceDeviceId}");
|
||||||
|
var owl = JsonSerializer.Deserialize<OwlDevice>(json);
|
||||||
|
return owl is null ? null : MapDevice(owl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StandardDevice>> GetAllDevicesAsync()
|
||||||
|
{
|
||||||
|
var result = new List<StandardDevice>();
|
||||||
|
int page = 1;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var paged = await GetDevicesAsync(page, 50);
|
||||||
|
result.AddRange(paged.Items);
|
||||||
|
if (result.Count >= paged.Total) break;
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
// Also get channels for each NVR
|
||||||
|
var channels = await GetAllChannelsAsync();
|
||||||
|
result.AddRange(channels);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<StandardDevice>> GetChannelsAsync(int page, int size, string? parentDeviceId = null)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var url = $"/channels?page={page}&size={size}";
|
||||||
|
if (!string.IsNullOrEmpty(parentDeviceId))
|
||||||
|
{
|
||||||
|
var resp = await client.GetStringAsync($"/devices/{parentDeviceId}/channels");
|
||||||
|
var channels = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(resp)!;
|
||||||
|
return new PagedResult<StandardDevice>
|
||||||
|
{
|
||||||
|
Items = channels.Items.Select(c => MapChannel(c)).ToList(),
|
||||||
|
Total = channels.Total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var json = await client.GetStringAsync(url);
|
||||||
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(json)!;
|
||||||
|
return new PagedResult<StandardDevice>
|
||||||
|
{
|
||||||
|
Items = owl.Items.Select(c => MapChannel(c)).ToList(),
|
||||||
|
Total = owl.Total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<StandardDevice>> GetAllChannelsAsync()
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var json = await client.GetStringAsync("/channels?size=1000");
|
||||||
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlChannel>>(json)!;
|
||||||
|
return owl.Items.Select(c => MapChannel(c)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── IHasStreams ───
|
||||||
|
public async Task<StreamUrls> GetLiveUrlAsync(string channelId)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var resp = await client.PostAsync($"/channels/{channelId}/play", null);
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
var json = await resp.Content.GetStringAsync();
|
||||||
|
var play = JsonSerializer.Deserialize<OwlPlayResponse>(json)!;
|
||||||
|
return MapStreamUrls(play);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StreamUrls> GetPlaybackUrlAsync(string channelId, DateTime start, DateTime end)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
||||||
|
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
||||||
|
var token = await _auth.GetTokenAsync();
|
||||||
|
return new StreamUrls
|
||||||
|
{
|
||||||
|
Hls = $"{client.BaseAddress}recordings/channels/{channelId}/index.m3u8?start_ms={startMs}&end_ms={endMs}&token={token}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopPlayAsync(string channelId)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
await client.PostAsync($"/channels/{channelId}/stop", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StreamUrls> GetSnapshotAsync(string channelId)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var resp = await client.PostAsync($"/channels/{channelId}/snapshot",
|
||||||
|
new StringContent("{}", System.Text.Encoding.UTF8, "application/json"));
|
||||||
|
var json = await resp.Content.ReadAsStringAsync();
|
||||||
|
var snap = JsonSerializer.Deserialize<OwlSnapshotResponse>(json)!;
|
||||||
|
return new StreamUrls { Hls = snap.Link };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PtzControlAsync(string channelId, string direction, float speed)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new
|
||||||
|
{
|
||||||
|
action = "continuous",
|
||||||
|
direction,
|
||||||
|
speed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PtzStopAsync(string channelId)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
await client.PostAsJsonAsync($"/channels/{channelId}/ptz/control", new
|
||||||
|
{
|
||||||
|
action = "stop"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PagedResult<StandardRecording>> GetRecordingsAsync(string channelId, DateTime start, DateTime end, int page, int size)
|
||||||
|
{
|
||||||
|
await _limiter.WaitAsync();
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var startMs = new DateTimeOffset(start).ToUnixTimeMilliseconds();
|
||||||
|
var endMs = new DateTimeOffset(end).ToUnixTimeMilliseconds();
|
||||||
|
var json = await client.GetStringAsync(
|
||||||
|
$"/recordings?cid={channelId}&start_ms={startMs}&end_ms={endMs}&page={page}&size={size}");
|
||||||
|
var owl = JsonSerializer.Deserialize<OwlPagedResult<OwlRecording>>(json)!;
|
||||||
|
return new PagedResult<StandardRecording>
|
||||||
|
{
|
||||||
|
Items = owl.Items.Select(r => new StandardRecording
|
||||||
|
{
|
||||||
|
Id = r.Id.ToString(),
|
||||||
|
ChannelId = r.Cid ?? "",
|
||||||
|
StartedAt = r.StartedAt,
|
||||||
|
EndedAt = r.EndedAt,
|
||||||
|
Duration = r.Duration,
|
||||||
|
FilePath = r.Path,
|
||||||
|
Size = r.Size
|
||||||
|
}).ToList(),
|
||||||
|
Total = owl.Total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── IAcceptsMetadataPush ───
|
||||||
|
public async Task<MetadataPushResult> PushMetadataAsync(string sourceDeviceId, MetadataChangeSet changes)
|
||||||
|
{
|
||||||
|
var client = await _auth.GetAuthenticatedClientAsync();
|
||||||
|
var body = new Dictionary<string, object>();
|
||||||
|
if (changes.Name != null) body["name"] = changes.Name;
|
||||||
|
var resp = await client.PutAsJsonAsync($"/devices/{sourceDeviceId}", body);
|
||||||
|
return new MetadataPushResult { Success = resp.IsSuccessStatusCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Mapping ───
|
||||||
|
private static StandardDevice MapDevice(OwlDevice d) => new()
|
||||||
|
{
|
||||||
|
SourceId = d.Id ?? "",
|
||||||
|
Name = d.Name ?? d.Id ?? "",
|
||||||
|
Category = "硬盘录像机",
|
||||||
|
Group = "视频设备",
|
||||||
|
IsOnline = d.IsOnline == "1",
|
||||||
|
IsParent = true,
|
||||||
|
IpAddress = d.Address,
|
||||||
|
Port = int.TryParse(d.Port, out var port) ? port : null,
|
||||||
|
Extra = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["owlDeviceId"] = d.Id,
|
||||||
|
["protocol"] = d.Protocol ?? "GB28181",
|
||||||
|
["transport"] = d.Transport
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static StandardDevice MapChannel(OwlChannel c) => new()
|
||||||
|
{
|
||||||
|
SourceId = c.Id ?? "",
|
||||||
|
Name = c.Name ?? c.Id ?? "",
|
||||||
|
Category = "摄像机",
|
||||||
|
Group = "视频设备",
|
||||||
|
IsOnline = c.IsOnline == "1",
|
||||||
|
IsParent = false,
|
||||||
|
ParentSourceId = c.DeviceId,
|
||||||
|
Extra = new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["owlChannelId"] = c.Id,
|
||||||
|
["streamApp"] = c.App,
|
||||||
|
["streamName"] = c.Stream,
|
||||||
|
["hasPtz"] = c.HasPtz == "1"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static StreamUrls MapStreamUrls(OwlPlayResponse play)
|
||||||
|
{
|
||||||
|
var item = play.Items?.FirstOrDefault();
|
||||||
|
return new StreamUrls
|
||||||
|
{
|
||||||
|
WsFlv = item?.WsFlv,
|
||||||
|
HttpFlv = item?.HttpFlv,
|
||||||
|
Hls = item?.Hls,
|
||||||
|
WebRtc = item?.WebRtc,
|
||||||
|
Rtmp = item?.Rtmp,
|
||||||
|
Rtsp = item?.Rtsp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Owl JSON Models ───
|
||||||
|
public class OwlPagedResult<T> { public List<T> Items { get; set; } = new(); public int Total { get; set; } }
|
||||||
|
public class OwlDevice
|
||||||
|
{
|
||||||
|
public string? Id { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? IsOnline { get; set; }
|
||||||
|
public string? Protocol { get; set; }
|
||||||
|
public string? Address { get; set; }
|
||||||
|
public string? Port { get; set; }
|
||||||
|
public string? Transport { get; set; }
|
||||||
|
public int Channels { get; set; }
|
||||||
|
}
|
||||||
|
public class OwlChannel
|
||||||
|
{
|
||||||
|
public string? Id { get; set; }
|
||||||
|
public string? DeviceId { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public string? IsOnline { get; set; }
|
||||||
|
public string? App { get; set; }
|
||||||
|
public string? Stream { get; set; }
|
||||||
|
public string? HasPtz { get; set; }
|
||||||
|
public string? HasRecording { get; set; }
|
||||||
|
public int RecordMode { get; set; }
|
||||||
|
}
|
||||||
|
public class OwlPlayResponse
|
||||||
|
{
|
||||||
|
public string? App { get; set; }
|
||||||
|
public string? Stream { get; set; }
|
||||||
|
public List<OwlPlayItem>? Items { get; set; }
|
||||||
|
}
|
||||||
|
public class OwlPlayItem
|
||||||
|
{
|
||||||
|
public string? Label { get; set; }
|
||||||
|
public string? WsFlv { get; set; }
|
||||||
|
public string? HttpFlv { get; set; }
|
||||||
|
public string? Hls { get; set; }
|
||||||
|
public string? WebRtc { get; set; }
|
||||||
|
public string? Rtmp { get; set; }
|
||||||
|
public string? Rtsp { get; set; }
|
||||||
|
}
|
||||||
|
public class OwlSnapshotResponse { public string? Link { get; set; } }
|
||||||
|
public class OwlRecording
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string? Cid { get; set; }
|
||||||
|
public DateTime StartedAt { get; set; }
|
||||||
|
public DateTime EndedAt { get; set; }
|
||||||
|
public double Duration { get; set; }
|
||||||
|
public string? Path { get; set; }
|
||||||
|
public long Size { get; set; }
|
||||||
|
}
|
||||||
61
gateway/src/IntegrationGateway.Adapters.Owl/OwlAuthHelper.cs
Normal file
61
gateway/src/IntegrationGateway.Adapters.Owl/OwlAuthHelper.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace IntegrationGateway.Adapters.Owl;
|
||||||
|
|
||||||
|
public class OwlAuthHelper
|
||||||
|
{
|
||||||
|
private readonly HttpClient _http;
|
||||||
|
private readonly string _baseUrl;
|
||||||
|
private readonly string _username;
|
||||||
|
private readonly string _password;
|
||||||
|
private string? _token;
|
||||||
|
private DateTime _tokenExpiry = DateTime.MinValue;
|
||||||
|
|
||||||
|
public OwlAuthHelper(HttpClient http, string baseUrl, string username, string password)
|
||||||
|
{
|
||||||
|
_http = http;
|
||||||
|
_baseUrl = baseUrl.TrimEnd('/');
|
||||||
|
_username = username;
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetTokenAsync()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_token) && DateTime.UtcNow < _tokenExpiry)
|
||||||
|
return _token;
|
||||||
|
|
||||||
|
var keyResp = await _http.GetStringAsync($"{_baseUrl}/login/key");
|
||||||
|
var keyData = JsonSerializer.Deserialize<LoginKeyResponse>(keyResp);
|
||||||
|
var publicKey = Encoding.UTF8.GetString(Convert.FromBase64String(keyData!.Key!));
|
||||||
|
|
||||||
|
using var rsa = RSA.Create();
|
||||||
|
rsa.ImportFromPem(publicKey);
|
||||||
|
var plain = JsonSerializer.Serialize(new { username = _username, password = _password });
|
||||||
|
var encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(plain), RSAEncryptionPadding.Pkcs1);
|
||||||
|
var payload = JsonSerializer.Serialize(new { data = Convert.ToBase64String(encrypted) });
|
||||||
|
|
||||||
|
var resp = await _http.PostAsync($"{_baseUrl}/login",
|
||||||
|
new StringContent(payload, Encoding.UTF8, "application/json"));
|
||||||
|
resp.EnsureSuccessStatusCode();
|
||||||
|
var loginResult = await resp.Content.ReadFromJsonAsync<LoginResponse>();
|
||||||
|
|
||||||
|
_token = loginResult!.Token;
|
||||||
|
_tokenExpiry = DateTime.UtcNow.AddDays(2.5);
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invalidate() => _token = null;
|
||||||
|
|
||||||
|
public async Task<HttpClient> GetAuthenticatedClientAsync()
|
||||||
|
{
|
||||||
|
var token = await GetTokenAsync();
|
||||||
|
var client = new HttpClient { BaseAddress = new Uri(_baseUrl) };
|
||||||
|
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoginKeyResponse { public string? Key { get; set; } }
|
||||||
|
public class LoginResponse { public string Token { get; set; } = ""; public string? User { get; set; } }
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\IntegrationGateway.Core\IntegrationGateway.Core.csproj" />
|
<ProjectReference Include="..\IntegrationGateway.Core\IntegrationGateway.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\IntegrationGateway.Adapters.Owl\IntegrationGateway.Adapters.Owl.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using IntegrationGateway.Core.Infrastructure;
|
using IntegrationGateway.Core.Infrastructure;
|
||||||
|
using IntegrationGateway.Adapters.Owl;
|
||||||
using IntegrationGateway.Host;
|
using IntegrationGateway.Host;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -23,6 +24,19 @@ app.Lifetime.ApplicationStarted.Register(() =>
|
|||||||
{
|
{
|
||||||
var gw = app.Services.GetRequiredService<GatewayClient>();
|
var gw = app.Services.GetRequiredService<GatewayClient>();
|
||||||
var registry = app.Services.GetRequiredService<AdapterRegistry>();
|
var registry = app.Services.GetRequiredService<AdapterRegistry>();
|
||||||
|
|
||||||
|
// 注册 Owl 适配器
|
||||||
|
var owlCfg = builder.Configuration.GetSection("Owl");
|
||||||
|
var owlHttp = app.Services.GetRequiredService<IHttpClientFactory>().CreateClient("VolPro");
|
||||||
|
var owlAdapter = new OwlAdapter(
|
||||||
|
"Owl:main",
|
||||||
|
owlHttp,
|
||||||
|
owlCfg["BaseUrl"] ?? "http://localhost:15123",
|
||||||
|
owlCfg["Username"] ?? "admin",
|
||||||
|
owlCfg["Password"] ?? "admin"
|
||||||
|
);
|
||||||
|
registry.Register(owlAdapter);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await gw.RegisterAsync();
|
var result = await gw.RegisterAsync();
|
||||||
|
|||||||
@@ -9,5 +9,10 @@
|
|||||||
"Urls": "http://*:5100",
|
"Urls": "http://*:5100",
|
||||||
"VolProBaseUrl": "http://localhost:9100",
|
"VolProBaseUrl": "http://localhost:9100",
|
||||||
"NodeCode": "gw-31ku",
|
"NodeCode": "gw-31ku",
|
||||||
"NodeToken": "xxxxxxxxxx"
|
"NodeToken": "xxxxxxxxxx",
|
||||||
|
"Owl": {
|
||||||
|
"BaseUrl": "http://owl_host:15123",
|
||||||
|
"Username": "admin",
|
||||||
|
"Password": "your_owl_password"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user