diff --git a/Realtime/Internal/Controller/LCIMMessageController.cs b/Realtime/Internal/Controller/LCIMMessageController.cs index 8378080..71707a5 100644 --- a/Realtime/Internal/Controller/LCIMMessageController.cs +++ b/Realtime/Internal/Controller/LCIMMessageController.cs @@ -32,7 +32,7 @@ namespace LeanCloud.Realtime.Internal.Controller { } else if (message is LCIMBinaryMessage binaryMessage) { direct.BinaryMsg = ByteString.CopyFrom(binaryMessage.Data); } else { - throw new ArgumentException("Message MUST BE LCIMTypedMessage or LCIMBinaryMessage."); + throw new ArgumentException("Message MUST be LCIMTypedMessage or LCIMBinaryMessage."); } // 暂态消息 if (options.Transient) { diff --git a/Realtime/Message/LCIMAudioMessage.cs b/Realtime/Message/LCIMAudioMessage.cs index fbc28e5..ead335e 100644 --- a/Realtime/Message/LCIMAudioMessage.cs +++ b/Realtime/Message/LCIMAudioMessage.cs @@ -2,14 +2,15 @@ using LeanCloud.Storage; namespace LeanCloud.Realtime { + /// + /// 音频消息 + /// public class LCIMAudioMessage : LCIMFileMessage { + /// + /// 时长 + /// public double Duration { - get { - if (double.TryParse(File.MetaData["duration"] as string, out double duration)) { - return duration; - } - return 0; - } + get; private set; } internal LCIMAudioMessage() { @@ -21,14 +22,22 @@ namespace LeanCloud.Realtime { internal override Dictionary Encode() { Dictionary data = base.Encode(); - Dictionary fileData = data["_lcfile"] as Dictionary; - Dictionary metaData = fileData["metaData"] as Dictionary; - if (File.MetaData.TryGetValue("duration", out object duration)) { - metaData["duration"] = duration; + Dictionary fileData = data[MessageFileKey] as Dictionary; + Dictionary metaData = fileData[MessageDataMetaDataKey] as Dictionary; + if (File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration)) { + metaData[MessageDataMetaDurationKey] = duration; } return data; } - internal override int MessageType => AudioMessageType; + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + if (File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration) && + double.TryParse(duration as string, out double d)) { + Duration = d; + } + } + + public override int MessageType => AudioMessageType; } } diff --git a/Realtime/Message/LCIMBinaryMessage.cs b/Realtime/Message/LCIMBinaryMessage.cs index d0af326..d8d76fa 100644 --- a/Realtime/Message/LCIMBinaryMessage.cs +++ b/Realtime/Message/LCIMBinaryMessage.cs @@ -1,5 +1,11 @@ namespace LeanCloud.Realtime { + /// + /// 二进制消息 + /// public class LCIMBinaryMessage : LCIMMessage { + /// + /// 消息数据 + /// public byte[] Data { get; internal set; } diff --git a/Realtime/Message/LCIMFileMessage.cs b/Realtime/Message/LCIMFileMessage.cs index 55aad4d..27c82b5 100644 --- a/Realtime/Message/LCIMFileMessage.cs +++ b/Realtime/Message/LCIMFileMessage.cs @@ -3,29 +3,41 @@ using System.Collections.Generic; using LeanCloud.Storage; namespace LeanCloud.Realtime { + /// + /// 文件消息 + /// public class LCIMFileMessage : LCIMTextMessage { + /// + /// 文件 + /// public LCFile File { get; set; } + /// + /// 文件大小 + /// public int Size { get { - if (int.TryParse(File.MetaData["size"] as string, out int size)) { + if (int.TryParse(File.MetaData[MessageDataMetaSizeKey] as string, out int size)) { return size; } return 0; } } + /// + /// 文件类型 + /// public string Format { get { - if (File.MetaData.TryGetValue("format", out object format)) { - return format as string; - } - return "unknown/unknown"; + return File.MimeType; } } + /// + /// 文件链接 + /// public string Url { get { return File.Url; @@ -40,41 +52,46 @@ namespace LeanCloud.Realtime { File = file; } - internal override int MessageType => FileMessageType; + public override int MessageType => FileMessageType; internal override Dictionary Encode() { if (File == null) { throw new Exception("File MUST NOT be null before sent."); } Dictionary fileData = new Dictionary { - { "objId", File.ObjectId }, - { "url", File.Url }, - { "metaData", new Dictionary { - { "name", File.Name }, - { "format", File.MimeType } + { MessageDataObjectIdKey, File.ObjectId }, + { MessageDataUrlKey, File.Url }, + { MessageDataMetaDataKey, new Dictionary { + { MessageDataMetaNameKey, File.Name }, + { MessageDataMetaFormatKey, File.MimeType } } } }; - if (File.MetaData.TryGetValue("size", out object size)) { - Dictionary metaData = fileData["metaData"] as Dictionary; - metaData["size"] = size; + if (File.MetaData.TryGetValue(MessageDataMetaSizeKey, out object size)) { + Dictionary metaData = fileData[MessageDataMetaDataKey] as Dictionary; + metaData[MessageDataMetaSizeKey] = size; } Dictionary data = base.Encode(); - data["_lcfile"] = fileData; + data[MessageFileKey] = fileData; return data; } - protected override void DecodeMessageData(Dictionary msgData) { - base.DecodeMessageData(msgData); - Dictionary fileData = msgData["_lcfile"] as Dictionary; - string objectId = fileData["objId"] as string; - File = LCObject.CreateWithoutData(LCFile.CLASS_NAME, objectId) as LCFile; - if (fileData.TryGetValue("url", out object url)) { - File.Url = url as string; - } - if (fileData.TryGetValue("metaData", out object metaData)) { - File.MetaData = metaData as Dictionary; - if (File.MetaData.TryGetValue("name", out object name)) { - File.Name = name as string; + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + + if (msgData.TryGetValue(MessageFileKey, out object fileDataObject)) { + Dictionary fileData = fileDataObject as Dictionary; + if (fileData.TryGetValue(MessageDataObjectIdKey, out object objectIdObject)) { + string objectId = objectIdObject as string; + File = LCObject.CreateWithoutData(LCFile.CLASS_NAME, objectId) as LCFile; + if (fileData.TryGetValue(MessageDataUrlKey, out object url)) { + File.Url = url as string; + } + if (fileData.TryGetValue(MessageDataMetaDataKey, out object metaData)) { + File.MetaData = metaData as Dictionary; + if (File.MetaData.TryGetValue(MessageDataMetaNameKey, out object name)) { + File.Name = name as string; + } + } } } } diff --git a/Realtime/Message/LCIMImageMessage.cs b/Realtime/Message/LCIMImageMessage.cs index fcf59ef..7d116d2 100644 --- a/Realtime/Message/LCIMImageMessage.cs +++ b/Realtime/Message/LCIMImageMessage.cs @@ -2,23 +2,22 @@ using LeanCloud.Storage; namespace LeanCloud.Realtime { + /// + /// 图像消息 + /// public class LCIMImageMessage : LCIMFileMessage { + /// + /// 图像宽度 + /// public int Width { - get { - if (int.TryParse(File.MetaData["width"] as string, out int width)) { - return width; - } - return 0; - } + get; private set; } + /// + /// 图像高度 + /// public int Height { - get { - if (int.TryParse(File.MetaData["height"] as string, out int height)) { - return height; - } - return 0; - } + get; private set; } internal LCIMImageMessage() : base() { @@ -30,17 +29,29 @@ namespace LeanCloud.Realtime { internal override Dictionary Encode() { Dictionary data = base.Encode(); - Dictionary fileData = data["_lcfile"] as Dictionary; - Dictionary metaData = fileData["metaData"] as Dictionary; - if (File.MetaData.TryGetValue("width", out object width)) { - metaData["width"] = width; + Dictionary fileData = data[MessageFileKey] as Dictionary; + Dictionary metaData = fileData[MessageDataMetaDataKey] as Dictionary; + if (File.MetaData.TryGetValue(MessageDataMetaWidthKey, out object width)) { + metaData[MessageDataMetaWidthKey] = width; } - if (File.MetaData.TryGetValue("height", out object height)) { - metaData["height"] = height; + if (File.MetaData.TryGetValue(MessageDataMetaHeightKey, out object height)) { + metaData[MessageDataMetaHeightKey] = height; } return data; } - internal override int MessageType => ImageMessageType; + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + if (File.MetaData.TryGetValue(MessageDataMetaWidthKey, out object width) && + int.TryParse(width as string, out int w)) { + Width = w; + } + if (File.MetaData.TryGetValue(MessageDataMetaHeightKey, out object height) && + int.TryParse(height as string, out int h)) { + Height = h; + } + } + + public override int MessageType => ImageMessageType; } } diff --git a/Realtime/Message/LCIMLocationMessage.cs b/Realtime/Message/LCIMLocationMessage.cs index ffe60c1..310da36 100644 --- a/Realtime/Message/LCIMLocationMessage.cs +++ b/Realtime/Message/LCIMLocationMessage.cs @@ -2,7 +2,13 @@ using LeanCloud.Storage; namespace LeanCloud.Realtime { + /// + /// 位置消息 + /// public class LCIMLocationMessage : LCIMTextMessage { + /// + /// 位置 + /// public LCGeoPoint Location { get; set; } @@ -17,19 +23,30 @@ namespace LeanCloud.Realtime { internal override Dictionary Encode() { Dictionary data = base.Encode(); Dictionary locationData = new Dictionary { - { "longitude", Location.Longitude }, - { "latitude", Location.Latitude } + { MessageDataLongitudeKey, Location.Longitude }, + { MessageDataLatitudeKey, Location.Latitude } }; - data["_lcloc"] = locationData; + data[MessageLocationKey] = locationData; return data; } - protected override void DecodeMessageData(Dictionary msgData) { - base.DecodeMessageData(msgData); - Dictionary locationData = msgData["_lcloc"] as Dictionary; - Location = new LCGeoPoint((double)locationData["latitude"], (double)locationData["longitude"]); + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + if (msgData.TryGetValue(MessageLocationKey, out object val)) { + Dictionary locationData = val as Dictionary; + double latitude = 0, longitude = 0; + if (locationData.TryGetValue(MessageDataLatitudeKey, out object lat) && + double.TryParse(lat as string, out double la)) { + latitude = la; + } + if (locationData.TryGetValue(MessageDataLongitudeKey, out object lon) && + double.TryParse(lon as string, out double lo)) { + longitude = lo; + } + Location = new LCGeoPoint(latitude, longitude); + } } - internal override int MessageType => LocationMessageType; + public override int MessageType => LocationMessageType; } } diff --git a/Realtime/Message/LCIMMessage.cs b/Realtime/Message/LCIMMessage.cs index 77fc128..e62fbcb 100644 --- a/Realtime/Message/LCIMMessage.cs +++ b/Realtime/Message/LCIMMessage.cs @@ -2,31 +2,41 @@ using System.Collections.Generic; namespace LeanCloud.Realtime { + /// + /// 消息基类 + /// public abstract class LCIMMessage { - internal const int TextMessageType = -1; - internal const int ImageMessageType = -2; - internal const int AudioMessageType = -3; - internal const int VideoMessageType = -4; - internal const int LocationMessageType = -5; - internal const int FileMessageType = -6; - internal const int RecalledMessageType = -127; - + /// + /// 消息所在对话 Id + /// public string ConversationId { get; set; } + /// + /// 消息 Id + /// public string Id { get; set; } + /// + /// 发送者 Id + /// public string FromClientId { get; set; } + /// + /// 发送时间戳 + /// public long SentTimestamp { get; internal set; } + /// + /// 发送时间 + /// public DateTime SentAt { get { return DateTimeOffset.FromUnixTimeMilliseconds(SentTimestamp) @@ -34,10 +44,16 @@ namespace LeanCloud.Realtime { } } + /// + /// 送达时间戳 + /// public long DeliveredTimestamp { get; internal set; } + /// + /// 送达时间 + /// public DateTime DeliveredAt { get { return DateTimeOffset.FromUnixTimeMilliseconds(DeliveredTimestamp) @@ -45,10 +61,16 @@ namespace LeanCloud.Realtime { } } + /// + /// 已读时间戳 + /// public long ReadTimestamp { get; internal set; } + /// + /// 已读时间 + /// public DateTime ReadAt { get { return DateTimeOffset.FromUnixTimeMilliseconds(ReadTimestamp) @@ -56,10 +78,16 @@ namespace LeanCloud.Realtime { } } + /// + /// 修改时间戳 + /// public long PatchedTimestamp { get; internal set; } + /// + /// 修改时间 + /// public DateTime PatchedAt { get { return DateTimeOffset.FromUnixTimeMilliseconds(PatchedTimestamp) @@ -67,18 +95,30 @@ namespace LeanCloud.Realtime { } } + /// + /// 提醒成员 Id 列表 + /// public List MentionIdList { get; set; } + /// + /// 是否提醒所有人 + /// public bool MentionAll { get; set; } + /// + /// 是否提醒当前用户 + /// public bool Mentioned { get; internal set; } + /// + /// 是否是暂态消息 + /// public bool IsTransient { get; internal set; } diff --git a/Realtime/Message/LCIMRecalledMessage.cs b/Realtime/Message/LCIMRecalledMessage.cs index 3706dda..352e585 100644 --- a/Realtime/Message/LCIMRecalledMessage.cs +++ b/Realtime/Message/LCIMRecalledMessage.cs @@ -7,6 +7,6 @@ namespace LeanCloud.Realtime { public LCIMRecalledMessage() { } - internal override int MessageType => RecalledMessageType; + public override int MessageType => RecalledMessageType; } } diff --git a/Realtime/Message/LCIMTextMessage.cs b/Realtime/Message/LCIMTextMessage.cs index 8a8ba8c..65244cb 100644 --- a/Realtime/Message/LCIMTextMessage.cs +++ b/Realtime/Message/LCIMTextMessage.cs @@ -1,7 +1,13 @@ using System.Collections.Generic; namespace LeanCloud.Realtime { + /// + /// 文本消息 + /// public class LCIMTextMessage : LCIMTypedMessage { + /// + /// 文本 + /// public string Text { get; set; } @@ -16,16 +22,16 @@ namespace LeanCloud.Realtime { internal override Dictionary Encode() { Dictionary data = base.Encode(); if (!string.IsNullOrEmpty(Text)) { - data["_lctext"] = Text; + data[MessageTextKey] = Text; } return data; } - internal override int MessageType => TextMessageType; + public override int MessageType => TextMessageType; - protected override void DecodeMessageData(Dictionary msgData) { - base.DecodeMessageData(msgData); - if (msgData.TryGetValue("_lctext", out object value)) { + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + if (msgData.TryGetValue(MessageTextKey, out object value)) { Text = value as string; } } diff --git a/Realtime/Message/LCIMTypedMessage.cs b/Realtime/Message/LCIMTypedMessage.cs index 08a5d6d..3c1b677 100644 --- a/Realtime/Message/LCIMTypedMessage.cs +++ b/Realtime/Message/LCIMTypedMessage.cs @@ -1,16 +1,85 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Newtonsoft.Json; using LeanCloud.Storage.Internal.Codec; using LeanCloud.Storage.Internal; namespace LeanCloud.Realtime { - public abstract class LCIMTypedMessage : LCIMMessage { + /// + /// 已知类型消息 + /// + public class LCIMTypedMessage : LCIMMessage { + /// + /// 文本消息 + /// + public const int TextMessageType = -1; + /// + /// 图像消息 + /// + public const int ImageMessageType = -2; + /// + /// 音频消息 + /// + public const int AudioMessageType = -3; + /// + /// 视频消息 + /// + public const int VideoMessageType = -4; + /// + /// 位置消息 + /// + public const int LocationMessageType = -5; + /// + /// 文件消息 + /// + public const int FileMessageType = -6; + /// + /// 撤回消息 + /// + public const int RecalledMessageType = -127; + + /// + /// 保留字段 + /// + protected const string MessageTypeKey = "_lctype"; + protected const string MessageAttributesKey = "_lcattrs"; + protected const string MessageTextKey = "_lctext"; + protected const string MessageLocationKey = "_lcloc"; + protected const string MessageFileKey = "_lcfile"; + + protected const string MessageDataLongitudeKey = "longitude"; + protected const string MessageDataLatitudeKey = "latitude"; + + protected const string MessageDataObjectIdKey = "objId"; + protected const string MessageDataUrlKey = "url"; + protected const string MessageDataMetaDataKey = "metaData"; + protected const string MessageDataMetaNameKey = "name"; + protected const string MessageDataMetaFormatKey = "format"; + protected const string MessageDataMetaSizeKey = "size"; + protected const string MessageDataMetaWidthKey = "width"; + protected const string MessageDataMetaHeightKey = "height"; + protected const string MessageDataMetaDurationKey = "duration"; + + private Dictionary customProperties; - internal virtual int MessageType { + /// + /// 完整的消息数据 + /// + protected Dictionary data = new Dictionary(); + + /// + /// 消息类型 + /// + public virtual int MessageType { get; private set; } + /// + /// 消息属性访问 + /// + /// + /// public object this[string key] { get { if (customProperties == null) { @@ -30,18 +99,19 @@ namespace LeanCloud.Realtime { } internal virtual Dictionary Encode() { - Dictionary msgData = new Dictionary { - { "_lctype", MessageType } - }; + Dictionary msgData = data != null ? + new Dictionary(data) : new Dictionary(); + msgData[MessageTypeKey] = MessageType; if (customProperties != null && customProperties.Count > 0) { - msgData["_lcattrs"] = LCEncoder.Encode(customProperties); + msgData[MessageAttributesKey] = LCEncoder.Encode(customProperties); } return msgData; } - protected virtual void DecodeMessageData(Dictionary msgData) { - MessageType = (int)msgData["_lctype"]; - if (msgData.TryGetValue("_lcattrs", out object attrObj)) { + internal virtual void Decode(Dictionary msgData) { + data = msgData; + MessageType = (int)msgData[MessageTypeKey]; + if (msgData.TryGetValue(MessageAttributesKey, out object attrObj)) { customProperties = LCDecoder.Decode(attrObj) as Dictionary; } } @@ -50,36 +120,38 @@ namespace LeanCloud.Realtime { Dictionary msgData = JsonConvert.DeserializeObject>(json, new LCJsonConverter()); LCIMTypedMessage message = null; - int msgType = (int)msgData["_lctype"]; - switch (msgType) { - case TextMessageType: - message = new LCIMTextMessage(); - break; - case ImageMessageType: - message = new LCIMImageMessage(); - break; - case AudioMessageType: - message = new LCIMAudioMessage(); - break; - case VideoMessageType: - message = new LCIMVideoMessage(); - break; - case LocationMessageType: - message = new LCIMLocationMessage(); - break; - case FileMessageType: - message = new LCIMFileMessage(); - break; - case RecalledMessageType: - message = new LCIMRecalledMessage(); - break; - default: - // TODO 用户自定义类型消息 - - break; + int msgType = (int)msgData[MessageTypeKey]; + if (customMessageDict.TryGetValue(msgType, out Func msgConstructor)) { + // 已注册的类型消息 + message = msgConstructor.Invoke(); + } else { + // 未注册的类型消息 + message = new LCIMTypedMessage(); } - message.DecodeMessageData(msgData); + message.Decode(msgData); return message; } + + // 内置已知类型消息 + static readonly Dictionary> customMessageDict = new Dictionary> { + { TextMessageType, () => new LCIMTextMessage() }, + { ImageMessageType, () => new LCIMImageMessage() }, + { AudioMessageType, () => new LCIMAudioMessage() }, + { VideoMessageType, () => new LCIMVideoMessage() }, + { LocationMessageType, () => new LCIMLocationMessage() }, + { FileMessageType, () => new LCIMFileMessage() }, + { RecalledMessageType, () => new LCIMRecalledMessage() } + }; + + /// + /// 注册自定义类型消息 + /// + /// + /// + /// + public static void Register(int msgType, Func msgConstructor) + where T : LCIMTypedMessage { + customMessageDict[msgType] = msgConstructor; + } } } diff --git a/Realtime/Message/LCIMVideoMessage.cs b/Realtime/Message/LCIMVideoMessage.cs index 9754dd6..09f6a91 100644 --- a/Realtime/Message/LCIMVideoMessage.cs +++ b/Realtime/Message/LCIMVideoMessage.cs @@ -2,14 +2,29 @@ using LeanCloud.Storage; namespace LeanCloud.Realtime { + /// + /// 视频消息 + /// public class LCIMVideoMessage : LCIMFileMessage { + /// + /// 宽度 + /// + public int Width { + get; private set; + } + + /// + /// 高度 + /// + public int Height { + get; private set; + } + + /// + /// 时长 + /// public double Duration { - get { - if (double.TryParse(File.MetaData["duration"] as string, out double duration)) { - return duration; - } - return 0; - } + get; private set; } internal LCIMVideoMessage() { @@ -21,20 +36,36 @@ namespace LeanCloud.Realtime { internal override Dictionary Encode() { Dictionary data = base.Encode(); - Dictionary fileData = data["_lcfile"] as Dictionary; - Dictionary metaData = fileData["metaData"] as Dictionary; - if (File.MetaData.TryGetValue("width", out object width)) { - metaData["width"] = width; + Dictionary fileData = data[MessageFileKey] as Dictionary; + Dictionary metaData = fileData[MessageDataMetaDataKey] as Dictionary; + if (File.MetaData.TryGetValue(MessageDataMetaWidthKey, out object width)) { + metaData[MessageDataMetaWidthKey] = width; } - if (File.MetaData.TryGetValue("height", out object height)) { - metaData["height"] = height; + if (File.MetaData.TryGetValue(MessageDataMetaHeightKey, out object height)) { + metaData[MessageDataMetaHeightKey] = height; } - if (File.MetaData.TryGetValue("duration", out object duration)) { - metaData["duration"] = duration; + if (File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration)) { + metaData[MessageDataMetaDurationKey] = duration; } return data; } - internal override int MessageType => VideoMessageType; + internal override void Decode(Dictionary msgData) { + base.Decode(msgData); + if (File.MetaData.TryGetValue(MessageDataMetaWidthKey, out object width) && + int.TryParse(width as string, out int w)) { + Width = w; + } + if (File.MetaData.TryGetValue(MessageDataMetaHeightKey, out object height) && + int.TryParse(height as string, out int h)) { + Height = h; + } + if (File.MetaData.TryGetValue(MessageDataMetaDurationKey, out object duration) && + double.TryParse(duration as string, out double d)) { + Duration = d; + } + } + + public override int MessageType => VideoMessageType; } }