DreamineVMS 1.0.0.0
Windows PC의 RTSP·USB 카메라 영상을 HLS로 변환해 원격 CCTV Viewer에 전달하는 데스크톱 에이전트입니다.
로딩중...
검색중...
일치하는것 없음
AgentApiClient.cs
이 파일의 문서화 페이지로 가기
1using System.Net.Http;
2using System.Net.Http.Json;
4using Microsoft.Extensions.Configuration;
5using Microsoft.Extensions.Logging;
6
8
17public sealed class AgentApiClient
18{
27 private readonly HttpClient _http;
36 private readonly ILogger<AgentApiClient> _logger;
45 private readonly List<Uri> _serverUrls;
54 private Uri? _activeServerUrl;
55
64 public string? Token { get; private set; }
73 public string? TenantId { get; private set; }
82 public List<AgentCameraInfo> Cameras { get; private set; } = [];
83
92 private string? _email;
101 private string? _password;
102
127 public AgentApiClient(IConfiguration config, ILogger<AgentApiClient> logger)
128 {
129 _logger = logger;
130 _serverUrls = BuildServerCandidates(config["Agent:ServerUrl"]);
131 _activeServerUrl = _serverUrls.FirstOrDefault();
132 _http = new HttpClient();
133 _email = config["Agent:Email"];
134 _password = config["Agent:Password"];
135 }
136
169 public void SetCredentials(string serverUrl, string email, string password)
170 {
171 _email = email;
172 _password = password;
173 var newUrls = BuildServerCandidates(serverUrl);
174 _serverUrls.Clear();
175 _serverUrls.AddRange(newUrls);
176 _activeServerUrl = _serverUrls.FirstOrDefault();
177 Token = null; // 재로그인 유도
178 }
179
188 public string? Email => _email;
197 public string? ServerUrl => _activeServerUrl?.ToString().TrimEnd('/');
198
215 public Task<(bool Ok, string Error)> LoginWithStoredCredentialsAsync()
216 {
217 if (string.IsNullOrWhiteSpace(_email) || string.IsNullOrWhiteSpace(_password))
218 return Task.FromResult((false, "이메일 또는 비밀번호가 설정되지 않았습니다."));
219 return LoginAsync(_email, _password);
220 }
221
254 public async Task<(bool Ok, string Error)> LoginAsync(string email, string password)
255 {
256 foreach (Uri serverUrl in EnumerateServerUrls())
257 {
258 try
259 {
260 var resp = await _http.PostAsJsonAsync(
261 new Uri(serverUrl, "/api/agent/login"),
262 new { Email = email, Password = password });
263
264 if (!resp.IsSuccessStatusCode)
265 {
266 _logger.LogWarning("[Agent] 로그인 실패: {Server} responded {StatusCode}.",
267 serverUrl, (int)resp.StatusCode);
268 continue;
269 }
270
271 var data = await resp.Content.ReadFromJsonAsync<AgentLoginResponse>();
272 if (data is null)
273 {
274 _logger.LogWarning("[Agent] 로그인 실패: {Server} returned an empty response.", serverUrl);
275 continue;
276 }
277
278 _activeServerUrl = serverUrl;
279 Token = data.Token;
280 TenantId = data.TenantId;
281 Cameras = data.Cameras ?? [];
282 _logger.LogInformation("[Agent] 중앙 서버 연결: {Server}", serverUrl);
283 return (true, "");
284 }
285 catch (Exception ex)
286 {
287 _logger.LogWarning(ex, "[Agent] 중앙 서버 연결 실패: {Server}", serverUrl);
288 }
289 }
290
291 Token = null;
292 return (false, "중앙 서버 연결 실패");
293 }
294
319 public async Task<bool> SyncCamerasAsync(IEnumerable<AgentCameraInfo> cameras)
320 {
321 if (Token is null) return false;
322 try
323 {
324 Uri serverUrl = GetActiveServerUrl();
325 var payload = new
326 {
327 Token,
328 Cameras = cameras.Select(c => new
329 {
330 c.Id, c.Name, c.Host, c.RtspUrl, c.AutoReconnect, c.IsPublic
331 })
332 };
333 var resp = await _http.PostAsJsonAsync(new Uri(serverUrl, "/api/agent/sync-cameras"), payload);
334 if (!resp.IsSuccessStatusCode)
335 {
336 _logger.LogWarning("[Agent] 카메라 동기화 실패: {Server} responded {StatusCode}.",
337 serverUrl, (int)resp.StatusCode);
338 }
339
340 return resp.IsSuccessStatusCode;
341 }
342 catch (Exception ex)
343 {
344 _logger.LogWarning(ex, "[Agent] 카메라 동기화 중 오류");
345 return false;
346 }
347 }
348
389 public async Task<bool> PushSegmentAsync(string cameraId, string filename, byte[] data)
390 {
391 if (Token is null) return false;
392 try
393 {
394 Uri serverUrl = GetActiveServerUrl();
395 using var content = new ByteArrayContent(data);
396 var resp = await _http.PostAsync(
397 new Uri(serverUrl, $"/api/agent/hls/{Token}/{cameraId}/{filename}"),
398 content);
399
400 if (resp.StatusCode == System.Net.HttpStatusCode.Unauthorized)
401 {
402 Token = null; // 재로그인 트리거
403 _logger.LogWarning("[Agent] 세그먼트 업로드 인증 만료: {CameraId}/{Filename}", cameraId, filename);
404 return false;
405 }
406
407 if (!resp.IsSuccessStatusCode)
408 {
409 _logger.LogWarning("[Agent] 세그먼트 업로드 실패: {CameraId}/{Filename} responded {StatusCode}.",
410 cameraId, filename, (int)resp.StatusCode);
411 }
412
413 return resp.IsSuccessStatusCode;
414 }
415 catch (Exception ex)
416 {
417 Token = null; // 서버 포트가 바뀌었거나 재시작된 경우 다음 루프에서 재로그인합니다.
418 _logger.LogWarning(ex, "[Agent] 세그먼트 업로드 중 오류: {CameraId}/{Filename}", cameraId, filename);
419 return false;
420 }
421 }
422
431 public void ClearToken() => Token = null;
432
449 private Uri GetActiveServerUrl()
450 {
451 return _activeServerUrl ?? _serverUrls.First();
452 }
453
470 private IEnumerable<Uri> EnumerateServerUrls()
471 {
472 if (_activeServerUrl is not null)
473 {
474 yield return _activeServerUrl;
475 }
476
477 foreach (Uri serverUrl in _serverUrls)
478 {
479 if (_activeServerUrl is not null && serverUrl == _activeServerUrl)
480 {
481 continue;
482 }
483
484 yield return serverUrl;
485 }
486 }
487
512 private static List<Uri> BuildServerCandidates(string? configuredUrl)
513 {
514 var candidates = new List<string>();
515 Add(configuredUrl);
516 Add("http://localhost:6080");
517 Add("https://localhost:6443");
518
519 return candidates
520 .Select(item => new Uri(item.EndsWith('/') ? item : item + "/"))
521 .Distinct()
522 .ToList();
523
524 #pragma warning disable CS1587
543 void Add(string? url)
544 {
545 if (string.IsNullOrWhiteSpace(url))
546 {
547 return;
548 }
549
550 candidates.Add(url.Trim());
551 }
552 #pragma warning restore CS1587
553 }
554}
555
564public sealed class AgentCameraInfo
565{
574 public string Id { get; set; } = "";
583 public string Name { get; set; } = "";
592 public string Host { get; set; } = "";
601 public string RtspUrl { get; set; } = "";
610 public bool AutoReconnect { get; set; }
619 public bool IsPublic { get; set; }
620}
621
630file sealed class AgentLoginResponse
631{
640 public string Token { get; set; } = "";
649 public string TenantId { get; set; } = "";
658 public List<AgentCameraInfo>? Cameras { get; set; }
659}
AgentApiClient(IConfiguration config, ILogger< AgentApiClient > logger)
readonly ILogger< AgentApiClient > _logger
static List< Uri > BuildServerCandidates(string? configuredUrl)
void SetCredentials(string serverUrl, string email, string password)
async Task< bool > PushSegmentAsync(string cameraId, string filename, byte[] data)
Task<(bool Ok, string Error)> LoginWithStoredCredentialsAsync()
async Task< bool > SyncCamerasAsync(IEnumerable< AgentCameraInfo > cameras)
async Task<(bool Ok, string Error)> LoginAsync(string email, string password)