核心数据类型转换
前言
前几集我们简单做了三条我们前后端交互接口的约定,简单看了我们的proto文件的内容,简单介绍了我们的Protobuf,并将protobuffer引入了我们的项目之中。
那么这一集我们就要把我们protobuffer的proto文件里的核心数据类型和Qt里的核心数据类型进行load转换!
需求分析
我们今天需要转换的核心数据类型就是proto文件里的UserInfo、ChatSessionInfo以及MessageInfo这三个核心数据类型。
包含proto的头文件
我们今天就要在我们的model/data.h里面添加以下内容。
#include "base.qpb.h"
简单说一下为什么要添加这一条代码
proto文件在我们Qt程序编译的时候,会把proto转换成cpp的代码。
转换的过程中,会生成同名的.h和.cc文件,这里的.cc文件其实就是.cpp文件。
我们的protobuffer官方包含头文件一般格式如下:
base.proto ---> base.pb.h
但是我们进入到Qt中就要入乡随俗
base.proto ---> base.qpb.h
可能有人发现了,我们添加了这一段东西进入我们的代码之中,会报错!下划线是红色的!
那么我们就需要先编译一遍这个程序,如果能够正常运行就是成功了的。如果没有运行成功,就请看上一集的内容。
如果成功运行,但是还是有红色的下划线,那么我们给他剪切一下重新粘贴上去即可。
这个时候下划线就可能变成黄色的了,那么我们就不需要管他了。我之前就是重新启动,黄色下划线就无了。
核心数据类型转换方法
那么我们也废话不多说,我们就开始今天的核心数据类型转换的方法的编写吧。
UserInfo
void load(const bite_im::UserInfo& userInfo){this->userId = userInfo.userId();this->nickname = userInfo.nickname();this->phone = userInfo.phone();this->description = userInfo.description();if(userInfo.avatar().isEmpty()){//使用默认头像this->avatar = QIcon(":/resource/image/defaultAvatar.png");}else{this->avatar = makeIcon(userInfo.avatar());}}
这里可以看到我们的bite_im这个命名空间,这个命名空间是我们的proto文件里面的。
可以看到我们的命名空间。
但是我们发现我们的proto文件是没有我们的get和set方法啊,这个我们是不需要担心的,这些方法我们的protobuffer都会给每个属性自动生成。虽然我们的命名是以下划线为分割,我们的Qt代码是驼峰,也不需要担心,我们的Qt的protobuffer的生成的方法,都会自动生成驼峰命名。
我们其实可以看到我们的头像还需要去判断是否为空,因为我们新创建的用户是没有我们的头像的,服务器这边存储的avatar就是一个空的字节数组。
MessageInfo
我们的MessageInfo最关键的内容就是我们的不同的消息类型来设置不同的content和messageType以及fileId这种细节。
void load(bite_im::MessageInfo& messageInfo){this->messageId = messageInfo.messageId();this->chatSessionId = messageInfo.chatSessionId();this->time = formatTime(messageInfo.timestamp());this->sender.load(messageInfo.sender());//消息类型auto type = messageInfo.message().messageType();if(type == bite_im::MessageTypeGadget::MessageType::STRING){this->messageType = TEXT_TYPE;this->content = messageInfo.message().stringMessage().content().toUtf8();}else if(type == bite_im::MessageTypeGadget::MessageType::IMAGE){this->messageType = IMAGE_TYPE;if(messageInfo.message().imageMessage().hasImageContent()){this->content = messageInfo.message().imageMessage().imageContent();}if(messageInfo.message().imageMessage().hasFileId()){this->fileId = messageInfo.message().imageMessage().fileId();}}else if(type == bite_im::MessageTypeGadget::MessageType::FILE){this->messageType = FILE_TYPE;if(messageInfo.message().fileMessage().hasFileContents()){this->content = messageInfo.message().fileMessage().fileContents();}if(messageInfo.message().fileMessage().hasFileId()){this->fileId = messageInfo.message().fileMessage().fileId();}this->fileName = messageInfo.message().fileMessage().fileName();}else if(type == bite_im::MessageTypeGadget::MessageType::SPEECH){this->messageType = SPEECH_TYPE;if(messageInfo.message().speechMessage().hasFileContents()){this->content = messageInfo.message().speechMessage().fileContents();}if(messageInfo.message().speechMessage().hasFileId()){this->fileId = messageInfo.message().speechMessage().fileId();}}else{//错误类型LOG() << "非法消息类型! type=" << type;}}
大家可能看着头疼,这个看起来也太乱了。
那么我们就对照着proto文件来写。
message StringMessageInfo {string content = 1;//文字聊天内容
}
message ImageMessageInfo {optional string file_id = 1;//图片文件id,客户端发送的时候不用设置,由transmit服务器进行设置后交给storage的时候设置optional bytes image_content = 2;//图片数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候需要原样转发
}
message FileMessageInfo {optional string file_id = 1;//文件id,客户端发送的时候不用设置int64 file_size = 2;//文件大小string file_name = 3;//文件名称optional bytes file_contents = 4;//文件数据,在ES中存储消息的时候只要id和元信息,不要文件数据, 服务端转发的时候也不需要填充
}
message SpeechMessageInfo {optional string file_id = 1;//语音文件id,客户端发送的时候不用设置optional bytes file_contents = 2;//文件数据,在ES中存储消息的时候只要id不要文件数据, 服务端转发的时候也不需要填充
}
message MessageContent {MessageType message_type = 1; //消息类型oneof msg_content {StringMessageInfo string_message = 2;//文字消息FileMessageInfo file_message = 3;//文件消息SpeechMessageInfo speech_message = 4;//语音消息ImageMessageInfo image_message = 5;//图片消息};
}
时间这个内容,就可以用到我们之前写到的工具函数formatTime
这代码中的MessageTypeGadget我们是不需要去管的!这个是protobuffer生成的内容。
我们的sender也是一个proto的一个类的对象,但是我们在前面的UserInfo里面也写了他的load方法,那么我们只需要调用load方法即可。
我们可以看到我们的图片信息的时候,我们的代码怎么还需要去看这个图片是否为空啊?怎么还要去看fileId啊?
因为我们的图片信息中,我们可能是直接携带图片的内容,也可能是图片的文件id。可以看到我们的proto文件当中,图像的content是一个optional修饰的变量。
ChatSessionInfo
void load(bite_im::ChatSessionInfo& chatSessionInfo){this->chatSessionId = chatSessionInfo.chatSessionId();this->chatSessionName = chatSessionInfo.chatSessionName();if(chatSessionInfo.hasSingleChatFriendId()){this->userId = chatSessionInfo.singleChatFriendId();}if(chatSessionInfo.hasPrevMessage()){lastMessage.load(chatSessionInfo.prevMessage());}if(chatSessionInfo.hasAvatar() && !(chatSessionInfo.avatar().isEmpty())){this->avatar = makeIcon(chatSessionInfo.avatar());}else{//没有头像,考虑单聊还是群聊if(userId != ""){//单聊this->avatar = QIcon(":/resource/image/defaultAvatar.png");}else{//群聊this->avatar = QIcon(":/resource/image/groupAvatar.png");}}}
我们这里的做法还是和上面的基本一致。这里我们的上一条消息就是直接load一下即可。之后我们就要去讨论这个头像了。
我们的头像在proto是一个QByteArray,我们需要转换成QIcon,就要使用我们之前写的工具函数!
如果没有头像,我们就需要分类讨论了,这里也是比较巧妙,如果没有userId就是群聊,有的话就是单聊。
如果是群聊就是以下头像。
那么这一集我们就先到这里。