ShengShengBuXi/ShengShengBuXi.ConsoleApp/Services/SignalRService.cs
2025-03-27 23:30:20 +08:00

362 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using Microsoft.AspNetCore.SignalR.Client;
using ShengShengBuXi.ConsoleApp.Models;
namespace ShengShengBuXi.ConsoleApp.Services;
/// <summary>
/// SignalR客户端服务实现
/// </summary>
public class SignalRService : ISignalRService
{
private HubConnection? _hubConnection;
private readonly string _configBackupPath;
private PhoneBoothConfig _currentConfig;
private readonly SemaphoreSlim _semaphore = new(1, 1);
private string? _clientId;
/// <summary>
/// 连接状态改变事件
/// </summary>
public event EventHandler<bool>? ConnectionStateChanged;
/// <summary>
/// 配置更新事件
/// </summary>
public event EventHandler<PhoneBoothConfig>? ConfigUpdated;
/// <summary>
/// 音频文件更新事件
/// </summary>
public event EventHandler<(string FileName, byte[] FileData)>? AudioFileUpdated;
/// <summary>
/// 获取当前是否已连接
/// </summary>
public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="configBackupPath">配置备份文件路径</param>
public SignalRService(string configBackupPath = "config.json")
{
_configBackupPath = configBackupPath;
// 加载本地配置
_currentConfig = PhoneBoothConfig.LoadFromFile(_configBackupPath);
}
/// <summary>
/// 启动连接
/// </summary>
/// <param name="hubUrl">SignalR Hub URL</param>
public async Task StartConnectionAsync(string hubUrl)
{
if (_hubConnection != null)
{
return;
}
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
// 注册连接关闭事件
_hubConnection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
ConnectionStateChanged?.Invoke(this, false);
await _hubConnection.StartAsync();
};
// 注册连接恢复事件
_hubConnection.Reconnected += connectionId =>
{
ConnectionStateChanged?.Invoke(this, true);
return Task.CompletedTask;
};
// 注册接收配置更新事件
_hubConnection.On<string>("ReceiveConfigUpdate", async (configJson) =>
{
try
{
var config = JsonSerializer.Deserialize<PhoneBoothConfig>(configJson);
if (config != null)
{
await UpdateConfigAsync(config);
}
}
catch (Exception ex)
{
Console.WriteLine($"处理配置更新失败: {ex.Message}");
}
});
// 注册接收音频文件更新事件
_hubConnection.On<string, byte[]>("ReceiveAudioFile", (fileName, fileData) =>
{
try
{
AudioFileUpdated?.Invoke(this, (fileName, fileData));
}
catch (Exception ex)
{
Console.WriteLine($"处理音频文件更新失败: {ex.Message}");
}
});
// 注册客户端ID确认事件
_hubConnection.On<string>("RegistrationConfirmed", (clientId) =>
{
_clientId = clientId;
Console.WriteLine($"客户端注册成功ID: {clientId}");
});
try
{
await _hubConnection.StartAsync();
ConnectionStateChanged?.Invoke(this, true);
// 连接成功后,注册为控制器客户端
await RegisterAsControllerAsync();
// 连接成功后,尝试获取最新配置
await RequestLatestConfigAsync();
}
catch (Exception ex)
{
Console.WriteLine($"SignalR连接失败: {ex.Message}");
ConnectionStateChanged?.Invoke(this, false);
}
}
/// <summary>
/// 注册为控制器客户端
/// </summary>
private async Task RegisterAsControllerAsync()
{
if (!IsConnected || _hubConnection == null)
{
return;
}
try
{
await _hubConnection.InvokeAsync("RegisterClient", ClientType.Controller, "电话亭控制器");
Console.WriteLine("已向服务器注册为控制器客户端");
}
catch (Exception ex)
{
Console.WriteLine($"注册客户端失败: {ex.Message}");
}
}
/// <summary>
/// 停止连接
/// </summary>
public async Task StopConnectionAsync()
{
if (_hubConnection != null)
{
try
{
// 如果有活动的音频流,先结束它
await EndAudioStreamAsync();
}
catch { /* 忽略错误 */ }
await _hubConnection.StopAsync();
await _hubConnection.DisposeAsync();
_hubConnection = null;
_clientId = null;
ConnectionStateChanged?.Invoke(this, false);
}
}
/// <summary>
/// 获取最新配置
/// </summary>
public async Task<PhoneBoothConfig> GetConfigAsync()
{
await _semaphore.WaitAsync();
try
{
// 如果连接正常,尝试从服务器获取最新配置
if (IsConnected)
{
await RequestLatestConfigAsync();
}
return _currentConfig;
}
finally
{
_semaphore.Release();
}
}
/// <summary>
/// 上传录音文件
/// </summary>
/// <param name="filePath">文件路径</param>
public async Task<bool> UploadRecordingAsync(string filePath)
{
// 检查配置是否允许上传录音文件
if (!_currentConfig.Recording.UploadRecordingToServer)
{
Console.WriteLine("根据配置设置,不上传录音文件");
return false;
}
if (!IsConnected || _hubConnection == null)
{
Console.WriteLine("无法上传录音文件:未连接到服务器");
return false;
}
try
{
if (!File.Exists(filePath))
{
Console.WriteLine($"无法上传录音文件:文件不存在 {filePath}");
return false;
}
string fileName = Path.GetFileName(filePath);
byte[] fileData = await File.ReadAllBytesAsync(filePath);
Console.WriteLine($"开始上传录音文件: {fileName}, 大小: {fileData.Length/1024} KB");
await _hubConnection.InvokeAsync("UploadRecording", fileName, fileData);
Console.WriteLine($"录音文件上传成功: {fileName}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"上传录音文件失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 开始音频流
/// </summary>
/// <param name="sampleRate">采样率</param>
/// <param name="channels">通道数</param>
public async Task StartAudioStreamAsync(int sampleRate, int channels)
{
if (!IsConnected || _hubConnection == null)
{
Console.WriteLine("无法开始音频流:未连接到服务器");
return;
}
try
{
await _hubConnection.InvokeAsync("StartAudioStream", sampleRate, channels);
Console.WriteLine($"已通知服务器开始接收音频流,采样率: {sampleRate}, 通道数: {channels}");
}
catch (Exception ex)
{
Console.WriteLine($"开始音频流失败: {ex.Message}");
}
}
/// <summary>
/// 结束音频流
/// </summary>
public async Task EndAudioStreamAsync()
{
if (!IsConnected || _hubConnection == null)
{
return;
}
try
{
await _hubConnection.InvokeAsync("EndAudioStream");
Console.WriteLine("已通知服务器结束音频流");
}
catch (Exception ex)
{
Console.WriteLine($"结束音频流失败: {ex.Message}");
}
}
/// <summary>
/// 实时上传音频数据
/// </summary>
/// <param name="audioData">音频数据字节数组</param>
public async Task<bool> UploadAudioDataRealtimeAsync(byte[] audioData)
{
if (!IsConnected || _hubConnection == null)
{
return false;
}
try
{
// 直接将音频数据发送到服务器
await _hubConnection.InvokeAsync("ReceiveAudioData", audioData);
return true;
}
catch (Exception ex)
{
Console.WriteLine($"实时上传音频数据失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 请求最新配置
/// </summary>
private async Task RequestLatestConfigAsync()
{
if (!IsConnected || _hubConnection == null)
{
return;
}
try
{
string configJson = await _hubConnection.InvokeAsync<string>("GetLatestConfig");
var config = JsonSerializer.Deserialize<PhoneBoothConfig>(configJson);
if (config != null)
{
await UpdateConfigAsync(config);
}
}
catch (Exception ex)
{
Console.WriteLine($"获取最新配置失败: {ex.Message}");
}
}
/// <summary>
/// 更新配置
/// </summary>
/// <param name="newConfig">新配置</param>
private async Task UpdateConfigAsync(PhoneBoothConfig newConfig)
{
await _semaphore.WaitAsync();
try
{
_currentConfig = newConfig;
// 保存到本地备份
_currentConfig.SaveToFile(_configBackupPath);
// 触发配置更新事件
ConfigUpdated?.Invoke(this, _currentConfig);
}
finally
{
_semaphore.Release();
}
}
}