AyCode.Core/AyCode.Services/SignalRs/IAcSignalRHubClient.cs

284 lines
8.2 KiB
C#

using AyCode.Core.Extensions;
using MessagePack;
using AyCode.Core.Interfaces;
using AyCode.Core.Compression;
using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute;
using STJIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute;
namespace AyCode.Services.SignalRs;
public class IdMessage
{
public List<string> Ids { get; private set; }
public IdMessage()
{
Ids = [];
}
/// <summary>
/// Creates IdMessage with multiple parameters serialized directly as JSON.
/// Each parameter is serialized independently without array wrapping.
/// Use object[] explicitly to pass multiple parameters.
/// </summary>
public IdMessage(object[] ids)
{
Ids = new List<string>(ids.Length);
for (var i = 0; i < ids.Length; i++)
{
Ids.Add(ids[i].ToJson());
}
}
/// <summary>
/// Creates IdMessage with a single parameter serialized as JSON.
/// Collections (List, Array, etc.) are serialized as a single JSON array.
/// </summary>
public IdMessage(object id)
{
Ids = new List<string>(1) { id.ToJson() };
}
/// <summary>
/// Creates IdMessage with multiple Guid parameters.
/// Each Guid is serialized as a separate Id entry.
/// </summary>
public IdMessage(IEnumerable<Guid> ids)
{
var idsArray = ids as Guid[] ?? ids.ToArray();
Ids = new List<string>(idsArray.Length);
for (var i = 0; i < idsArray.Length; i++)
{
Ids.Add(idsArray[i].ToJson());
}
}
public override string ToString() => string.Join("; ", Ids);
}
[MessagePackObject]
public class SignalPostJsonMessage
{
[Key(0)]
public string PostDataJson { get; set; } = "";
public SignalPostJsonMessage() { }
protected SignalPostJsonMessage(string postDataJson) => PostDataJson = postDataJson;
}
[MessagePackObject(AllowPrivate = false)]
public class SignalPostJsonDataMessage<TPostDataType> : SignalPostJsonMessage, ISignalPostMessage<TPostDataType>
{
[IgnoreMember]
[JsonIgnore]
[STJIgnore]
private TPostDataType? _postData;
[IgnoreMember]
[JsonIgnore]
[STJIgnore]
public TPostDataType PostData
{
get => _postData ??= PostDataJson.JsonTo<TPostDataType>()!;
private init
{
_postData = value;
PostDataJson = _postData.ToJson();
}
}
public SignalPostJsonDataMessage() : base() { }
public SignalPostJsonDataMessage(TPostDataType postData) => PostData = postData;
public SignalPostJsonDataMessage(string postDataJson) : base(postDataJson) { }
}
[MessagePackObject]
public class SignalPostMessage<TPostData>(TPostData postData) : ISignalPostMessage<TPostData>
{
[Key(0)]
public TPostData? PostData { get; set; } = postData;
}
public interface ISignalPostMessage<TPostData> : ISignalRMessage
{
TPostData? PostData { get; }
}
[MessagePackObject]
public class SignalRequestByIdMessage(Guid id) : ISignalRequestMessage<Guid>, IId<Guid>
{
[Key(0)]
public Guid Id { get; set; } = id;
}
public interface ISignalRequestMessage<TRequestId> : ISignalRMessage
{
TRequestId Id { get; set; }
}
public interface ISignalRMessage { }
public interface ISignalResponseMessage : ISignalRMessage
{
int MessageTag { get; set; }
SignalResponseStatus Status { get; set; }
}
public enum SignalResponseStatus : byte
{
Error = 0,
Success = 5
}
/// <summary>
/// Signal response message with lazy deserialization support.
/// Used for callback-based response handling.
/// </summary>
[MessagePackObject(AllowPrivate = false)]
public sealed class SignalResponseMessage<TResponseData> : ISignalResponseMessage
{
[IgnoreMember]
[JsonIgnore]
[STJIgnore]
private TResponseData? _responseData;
[IgnoreMember]
[JsonIgnore]
[STJIgnore]
private bool _isDeserialized;
[Key(0)]
public int MessageTag { get; set; }
[Key(1)]
public SignalResponseStatus Status { get; set; }
[Key(2)]
public string? ResponseDataJson { get; set; }
[IgnoreMember]
[JsonIgnore]
[STJIgnore]
public TResponseData? ResponseData
{
get
{
if (!_isDeserialized)
{
_isDeserialized = true;
_responseData = ResponseDataJson != null ? ResponseDataJson.JsonTo<TResponseData>() : default;
}
return _responseData;
}
set
{
_isDeserialized = true;
_responseData = value;
ResponseDataJson = value?.ToJson();
}
}
public SignalResponseMessage() { }
public SignalResponseMessage(int messageTag, SignalResponseStatus status)
{
MessageTag = messageTag;
Status = status;
}
public SignalResponseMessage(int messageTag, SignalResponseStatus status, TResponseData? responseData) : this(messageTag, status)
=> ResponseData = responseData;
public SignalResponseMessage(int messageTag, SignalResponseStatus status, string? responseDataJson) : this(messageTag, status)
=> ResponseDataJson = responseDataJson;
}
/// <summary>
/// Unified signal response message that supports both JSON and Binary serialization.
/// JSON mode uses Brotli compression for reduced payload size.
/// Optimized: decompression is performed only once and cached.
/// </summary>
public sealed class SignalResponseDataMessage : ISignalResponseMessage
{
public int MessageTag { get; set; }
public SignalResponseStatus Status { get; set; }
public AcSerializerType DataSerializerType { get; set; }
public byte[]? ResponseDataBin { get; set; }
[JsonIgnore] [STJIgnore] private string? _cachedJson;
[JsonIgnore] [STJIgnore] private object? _cachedResponseData;
public SignalResponseDataMessage()
{
}
public SignalResponseDataMessage(int messageTag, SignalResponseStatus status)
{
MessageTag = messageTag;
Status = status;
}
public SignalResponseDataMessage(int messageTag, SignalResponseStatus status, object? responseData, AcSerializerOptions serializerOptions)
: this(messageTag, status)
{
DataSerializerType = serializerOptions.SerializerType;
if (responseData == null)
{
ResponseDataBin = null;
return;
}
if (serializerOptions.SerializerType == AcSerializerType.Binary)
{
if (responseData is byte[] byteData)
{
ResponseDataBin = byteData;
return;
}
var binaryOptions = serializerOptions as AcBinarySerializerOptions ?? AcBinarySerializerOptions.Default;
ResponseDataBin = responseData.ToBinary(binaryOptions);
}
else
{
string json;
if (responseData is string strData)
{
var trimmed = strData.Trim();
if (trimmed.Length > 1 && (trimmed[0] == '{' || trimmed[0] == '[') && (trimmed[^1] == '}' || trimmed[^1] == ']'))
json = strData;
else
{
var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default;
json = responseData.ToJson(jsonOptions);
}
}
else
{
var jsonOptions = serializerOptions as AcJsonSerializerOptions ?? AcJsonSerializerOptions.Default;
json = responseData.ToJson(jsonOptions);
}
ResponseDataBin = BrotliHelper.Compress(json);
}
}
/// <summary>
/// Deserializes the ResponseData to the specified type.
/// Uses cached decompressed JSON for repeated calls.
/// </summary>
public T? GetResponseData<T>()
{
if (_cachedResponseData != null) return (T)_cachedResponseData;
if (ResponseDataBin == null) return default;
if (DataSerializerType == AcSerializerType.Binary) return (T)(_cachedResponseData = ResponseDataBin.BinaryTo<T>());
_cachedJson ??= BrotliHelper.DecompressToString(ResponseDataBin);
return (T)(_cachedResponseData = _cachedJson.JsonTo<T>());
}
}
public interface IAcSignalRHubClient : IAcSignalRHubBase
{
Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int? requestId);
}