ShengShengBuXi/ShengShengBuXi.ConsoleApp/Services/SignalRService.cs
2025-03-29 00:23:54 +08:00

468 lines
14 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;
private bool _isInitialConnection = true;
private const int MAX_RETRY_COUNT = 15;
private const int RETRY_INTERVAL_MS = 200;
private const int HEARTBEAT_INTERVAL_MS = 3000; // 3秒发送一次心跳
private Timer? _heartbeatTimer;
private DateTime _lastHeartbeatTime = DateTime.MinValue;
private const int HEARTBEAT_TIMEOUT_MS = 60000; // 60秒没有心跳就认为断开
/// <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)
{
try
{
await _hubConnection.StopAsync();
await _hubConnection.DisposeAsync();
_hubConnection = null;
}
catch (Exception ex)
{
Console.WriteLine($"停止旧连接失败: {ex.Message}");
}
}
_hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.WithAutomaticReconnect()
.Build();
// 启动心跳定时器
_heartbeatTimer?.Dispose();
_heartbeatTimer = new Timer(CheckHeartbeat, null, HEARTBEAT_INTERVAL_MS, HEARTBEAT_INTERVAL_MS);
_lastHeartbeatTime = DateTime.Now;
// 注册连接关闭事件
_hubConnection.Closed += async (error) =>
{
Console.WriteLine($"SignalR连接已关闭: {error?.Message ?? ""}");
if (_isInitialConnection)
{
// 初始连接失败,尝试重连
int retryCount = 0;
while (retryCount < MAX_RETRY_COUNT)
{
try
{
Console.WriteLine($"尝试重新连接服务器 (第{retryCount + 1}次)...");
await _hubConnection.StartAsync();
Console.WriteLine("重新连接成功");
ConnectionStateChanged?.Invoke(this, true);
return;
}
catch (Exception ex)
{
retryCount++;
Console.WriteLine($"重连失败: {ex.Message}");
if (retryCount >= MAX_RETRY_COUNT)
{
Console.WriteLine($"达到最大重试次数({MAX_RETRY_COUNT}),程序将退出");
Environment.Exit(1);
}
await Task.Delay(RETRY_INTERVAL_MS);
}
}
}
else
{
// 运行时的连接断开,直接退出程序
Console.WriteLine("SignalR连接断开程序将退出");
Environment.Exit(1);
}
};
// 注册连接恢复事件
_hubConnection.Reconnected += connectionId =>
{
_isInitialConnection = false;
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();
_isInitialConnection = false;
ConnectionStateChanged?.Invoke(this, true);
// 连接成功后,注册为控制器客户端
await RegisterAsControllerAsync();
// 连接成功后,尝试获取最新配置
await RequestLatestConfigAsync();
}
catch (Exception ex)
{
Console.WriteLine($"SignalR连接失败: {ex.Message}");
ConnectionStateChanged?.Invoke(this, false);
try
{
await _hubConnection.StopAsync();
await _hubConnection.DisposeAsync();
_hubConnection = null;
}
catch { /* 忽略清理时的错误 */ }
throw; // 抛出异常,让调用者处理
}
}
/// <summary>
/// 检查心跳
/// </summary>
private async void CheckHeartbeat(object? state)
{
try
{
if (_hubConnection == null || _hubConnection.State != HubConnectionState.Connected)
{
return;
}
// 检查上次心跳时间是否超时
if ((DateTime.Now - _lastHeartbeatTime).TotalMilliseconds > HEARTBEAT_TIMEOUT_MS)
{
Console.WriteLine("心跳超时,连接可能已断开");
await _hubConnection.StopAsync();
return;
}
// 发送心跳
await RequestLatestConfigAsync();
_lastHeartbeatTime = DateTime.Now;
}
catch (Exception ex)
{
Console.WriteLine($"心跳检测失败: {ex.Message}");
if (_hubConnection != null)
{
await _hubConnection.StopAsync();
}
}
}
/// <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);
}
// 停止心跳定时器
_heartbeatTimer?.Dispose();
_heartbeatTimer = null;
}
/// <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}");
// 如果获取配置失败,可能是连接已断开
if (_hubConnection != null)
{
await _hubConnection.StopAsync();
}
}
}
/// <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();
}
}
}