QT6 C++ 高仿QQ -- 10. 搜索好友, 添加好友消息结构和实现,protobuf消息与c++数据模型的序列化与反序列化

搜索好友与结果消息

//搜群与好友的类型枚举
enum FriendSearchType
{
	FRIEND		= 0;							//搜好友
	GROUP		= 1;							//搜群
	ALL  		= 2;							//搜好友 + 群
}
//client ---> server 搜好友或者搜群请求
message FriendSearchRequest
{
    uint64              request_id      = 1;        //请求序列
    string              key_word        = 2;
    FriendSearchType    search_type     = 3;
    uint32              current_page    = 4;        //第几页
    uint32              page_size       = 5;        //每页多少条数据,最多100
    
}

//server ---> client 搜好友或者搜群响应
message FriendSearchResponse
{
    uint64                  request_id      = 1;        //请求序列
    bool                    result          = 2;        //请求结果
    string                  reason          = 3;        //失败原因
    repeated UserInfo 	    friends	    	= 4;	    //此用户的所有朋友和分组信息
    repeated Group 			groups		    = 5;	    //群
    uint32                  current_page    = 6;        //第几页
    uint64                  total_page      = 7;        //总共多少页
    uint64                  total_num       = 8;        //总共多少条数据
    
}

用户信息消息

//用户信息
message UserInfo
{
    int64           code                = 1;    //用户号
    string          name                = 2;    //用户名, 系统登录使用 ---- 只有本人才下发, 朋友不下发登录名
    string          real_name           = 3;    //用户名, 系统登录使用 ---- 只有本人才下发, 朋友不下发登录名
    string          nick_name           = 4;    //用户昵称
    bytes           header_image        = 5;    //头像图片二进制raw数据
    string          remark              = 6;    //备注信息
    string          signature           = 7;    //个性签名
    GenderType      gender              = 8;    //性别: 1: man; 2: woman
    uint64          birthday            = 9;    //生日
    CityLocation    continent           = 10;   //州-国家-省份-城市
    CityLocation    country             = 11;   //州-国家-省份-城市
    CityLocation    province            = 12;   //州-国家-省份-城市
    CityLocation    city                = 13;   //州-国家-省份-城市
    string          email               = 14;   //email
    uint32          email_auth          = 15;   //是否认证, 1:已经认证
    string          mobile              = 16;   //绑定的手机号
	UserLevel		user_level			= 17;	//会员级别: 0:normal 普通会员 1: silver 银卡会员; 2: gold 金卡会员;3:platinum 白金会员
    string          device_id           = 18;   //用户登录的设备id
    bool            online              = 19;   //用户的在线状态
    
    string          group               = 30;    //密钥加密用
    bytes           salt                = 31;
    bytes           verifier            = 32;
}

初始化添加好友和发起群聊按钮

//设置添加好友和发起群聊按钮初始化
void ContactNavWidget::initButtons()
{
    //添加好友
    QPixmap pix = IconHelper::getPixmap("#696969", 0xf234, 18, 20, 20);//字体图标: fa-user-plus 灰色
    ui->btnAddFriend->setText("");      //清空文本信息
    ui->btnAddFriend->setIcon(QIcon(pix));
    ui->btnAddFriend->setToolTip(tr("Add a new friend or group"));  //添加按钮提示信息

    //发起群聊
    pix = IconHelper::getPixmap("#696969", 0xf0c0, 18, 20, 20);//字体图标: fa-users 灰色
    ui->btnStartGroupChat->setText(""); //清空文本信息
    ui->btnStartGroupChat->setIcon(QIcon(pix));
    ui->btnStartGroupChat->setToolTip(tr("Start group chat"));      //添加按钮提示信息
}

搜索好友请求

//添加好友或者加入其他群
void ContactNavWidget::on_btnAddFriend_clicked()
{
    newFriendDlg_.reset(new NewFriendDialog());

    newFriendDlg_->setWindowTitle(tr("Search new friends"));
    newFriendDlg_->setFriendGroup(friendGroups_);        //设置分组信息
    connect(newFriendDlg_.get(), &NewFriendDialog::searchTextChanged, this, &ContactNavWidget::searchFriendRequest);               //1. 请求搜索好友or群
    connect(newFriendDlg_.get(), &NewFriendDialog::friendAddRequest, this, &ContactNavWidget::onFriendAddRequest);                 //2. 加好友请求

    //打开对话框
    if (newFriendDlg_->exec() == QDialog::Accepted) {
    }

    newFriendDlg_.reset();
}
//向服务器发起搜索好友或者群
void ContactNavWidget::searchFriendRequest(QString keyWord, proto::FriendSearchType searchType, uint32_t currentPage, uint32_t pageSize)
{
    proto::internal::UiToService message;
    proto::FriendSearchRequest* request = message.mutable_friend_search_request();
    request->set_key_word(keyWord.toStdString());
    request->set_search_type(searchType);
    request->set_current_page(currentPage);
    request->set_page_size(pageSize);

    LOG(LS_INFO) << "Send search request, key words: " << keyWord.toStdString();
    emit sendMessageToIpc(message);
}

搜索好友结果处理

//好友搜索结果响应处理
void NewFriendDialog::onFriendSearchResponse(const proto::FriendSearchResponse& response)
{
    std::vector<base::UserInfo> users;
    int userCount = response.friends_size();
    for (int i = 0; i < userCount; i++) {
        base::UserInfo user;
        const proto::UserInfo& userMsg = response.friends(i);
        user.code = userMsg.code();
        user.headerImage = userMsg.header_image();
        user.name = base::utf16FromUtf8(userMsg.name());
        user.nickName = base::utf16FromUtf8(userMsg.nick_name());
        user.realName = base::utf16FromUtf8(userMsg.real_name());
        user.birthday = userMsg.birthday(); //年龄
        user.country = base::CityLocation::parseFrom(userMsg.country()); //省份
        user.province = base::CityLocation::parseFrom(userMsg.province()); //省份
        user.city = base::CityLocation::parseFrom(userMsg.city());         //城市
        user.remark = base::utf16FromUtf8(userMsg.remark());
        user.signature = base::utf16FromUtf8(userMsg.signature());
        user.gender = userMsg.gender();
        users.emplace_back(std::move(user));
    }

    //将搜索到的所有用户信息保存到本地
    users_ = std::move(users);
    currentPage_ = response.current_page();
    totalPage_ = response.total_page();
    totalNum_ = response.total_num();
    LOG(LS_INFO) << "search result, user count: " << userCount << ", page: " << currentPage_ << ", total page: " << totalPage_ << ", total number: " << totalNum_;
    //clearWidgets(mainLayout_);       //清空好友列表中所有item

    updateUserList();       //更新搜索结果用户列表
    updatePageOption();     //更新分页选项数据
}

好友列表显示到QListWidget

//更新好友列表
void NewFriendDialog::updateUserList()
{
    //将设备添加到设备列表中
    clearWidgets(contentLayout_);
    userControlList_.clear();//update前要清空之前的
    size_t listCout = users_.size();
    if (listCout <= 0) {
        return;
    }

    for (int i = 0; i < listCout; i++) {
        auto userControl = new NewUserControl(this, i);
        userControl->setUser(users_[i]);

        //设置背景样式
        userControl->setStyleSheet("QWidget{background-color: white; height: 60px}"           
        );
        userControl->setMinimumHeight(45);

        userControl->update();
        connect(userControl, &NewUserControl::friendAddRequest, this, &NewFriendDialog::onFriendAddRequest);//某个设备被选中的信号与槽连接
        contentLayout_->addWidget(userControl);
        contentLayout_->addSpacing(2);
        userControlList_.push_back(userControl);
    }

    contentLayout_->addStretch();                       //在最下面添加弹簧顶着
    contentLayout_->setContentsMargins(0, 0, 0, 0);     //内容上面添加8px边距
    contentWidget_->setLayout(contentLayout_);
    //contentWidget_->setStyleSheet("border:none");     //下面的listWidget设置无边框即可

    // 让QWidget撑满QScrollArea
    contentWidget_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    //将user list添加到滚动区域中
    scrollArea_->setWidget(contentWidget_);
    scrollArea_->setWidgetResizable(true);

    //主垂直布局添加滚动
    mainLayout_->addWidget(scrollArea_);
    mainLayout_->setContentsMargins(0, 0, 0, 0);        //设置内容无编辑
    ui->listWidget->setLayout(mainLayout_);
    ui->listWidget->setStyleSheet("border:none");       //设置列表无边框

    //刷新ui
    update();
}

//NewFriendDialog.h中核心数据结构:好友数组和自定义好友item数组
 //好友列表
 std::vector<NewUserControl*>  userControlList_; //搜索返回的好友列表
 std::vector<base::UserInfo>   users_;           //从上面的好友列表转换为数组

用户信息数据模型类 UserInfo

/****************************************************************************
**
** Copyright (C) 2024 The RelaxWalk Company Ltd.
** Contact: support@relaxwalk.com
** @Date 2024-03-20
** 用户模型定义
**
**
****************************************************************************/

#ifndef BASE__PEER__USER_INFO__H
#define BASE__PEER__USER_INFO__H

#include "base/memory/byte_array.h"
#include "model/city_location.h"
#include "proto/common.pb.h"

namespace base
{
    class UserInfo
    {
    public:
        UserInfo() = default;
        ~UserInfo() = default;

        UserInfo(const UserInfo& other) = default;
        UserInfo& operator=(const UserInfo& other) = default;

        UserInfo(UserInfo&& other) noexcept = default;
        UserInfo& operator=(UserInfo&& other) noexcept = default;

        static const size_t kMaxUserNameLength = 32;
        static const size_t kMaxHeaderImageSize = 1024 * 64;      //头像最大64k
        static const size_t kMinPasswordLength = 1;
        static const size_t kMaxPasswordLength = 32;
        static const size_t kSafePasswordLength = 8;

        static bool isValidUserName(std::u16string_view username);
        static bool isValidPassword(std::u16string_view password);
        static bool isSafePassword(std::u16string_view password);

        static UserInfo create(std::u16string_view name, std::u16string_view password, uint64_t userCode);
        bool isValid() const;
        bool isValidHeaderImage() const;

        static UserInfo parseFrom(const proto::UserInfo& serialized_user);
        proto::UserInfo serialize() const;

        static const UserInfo kInvalidUser;

        //用户基本信息
        int64_t    id = 0;          //数据库自增ID
        int64_t    code = 0;        //用户id 5-14位全局唯一数字
        std::u16string name = u"";        //用户名, 系统登录使用
        std::u16string realName = u"";    //真实姓名
        std::u16string nickName = u"";    //昵称
        std::string headerImage;    //二进制的头像图片数据, png格式, 最大64k
        std::string headerFormat;   //头像文件格式, 当前默认为png格式, 

        std::u16string remark = u"";      //备注 ---- 登录者不用
        std::u16string signature = u"";   //个性签名
        proto::GenderType   gender; //性别
        int64_t    birthday = 0;       //生日
        uint32_t    level = 0;          //会员级别

        int64_t continentId;       //州Id
        int64_t countryId;         // 国家代码
        int64_t provinceId;        // 省代码
        int64_t cityId;            // 城市代码
        base::CityLocation  continent = base::CityLocation::kInvalidLocation;
        base::CityLocation  country = base::CityLocation::kInvalidLocation;
        base::CityLocation  province = base::CityLocation::kInvalidLocation;
        base::CityLocation  city = base::CityLocation::kInvalidLocation;

        //下面三个是加密相关的信息
        std::string pwdGroup;     //密码group 组别
        std::string pwdSalt;      //盐值 64bytes BIN TO HEX
        std::string pwdVerifier;  //校验信息 BIN TO HEX

        std::string email;         //email信息
        int emailAuth = 0;         //是否通过email认证: 0: 未认证; 1: 认证成功
        int online = 0;            //在线状态
        std::string mobile;        //手机号
        int mobileAuth = 0;        //手机号认证标识: 0: 未认证; 1: 认证成功
        std::string deviceId;      //登录用户的设备id

        std::string revision;      //乐观锁
        std::string status;        //用户状态: 0: 禁用; 1: 启用; 2: 锁定; 3: 删除
        std::string createBy;      //创建人
        int64_t createTime = 0;      //创建时戳
        std::string updateBy;    //更新人
        int64_t updateTime = 0;      //更新时戳
    };

} // namespace base

#endif // BASE__PEER__USER_INFO__H


protobuf消息与模型之间的序列化与反序列化

/****************************************************************************
**
** Copyright (C) 2024 The RelaxWalk Company Ltd.
** Contact: support@relaxwalk.com
** @Date 2024-03-20
** 用户信息模型定义
**
**
****************************************************************************/

#include "model/user_info.h"

#include "base/base64.h"
#include "base/logging.h"
#include "base/crypto/random.h"
#include "base/crypto/srp_constants.h"
#include "base/crypto/srp_math.h"
#include "base/strings/unicode.h"
#include "base/utils.h"

#include <cwctype>

namespace base
{
    namespace
    {
        const size_t kSaltSize = 64; // In bytes.
        const char kDefaultGroup[] = "4096";

        bool isValidUserNameChar(char16_t username_char)
        {
            if (std::iswalnum(username_char))
                return true;

            if (username_char == '.' ||
                username_char == '_' ||
                username_char == '-') {
                return true;
            }

            return false;
        }

    } // namespace

    // static
    const UserInfo UserInfo::kInvalidUser{};


    // 用户名校验, 小于64个字
    bool UserInfo::isValidUserName(std::u16string_view username)
    {
        size_t length = username.length();

        if (!length || length > kMaxUserNameLength)
            return false;

        //用户名中只能是字母/数字/. - _等字符
        for (size_t i = 0; i < length; ++i) {
            if (!isValidUserNameChar(username[i]))
                return false;
        }
        return true;
    }

    // 密码长度大于1小于32个字符
    bool UserInfo::isValidPassword(std::u16string_view password)
    {
        size_t length = password.length();

        if (length < kMinPasswordLength || length > kMaxPasswordLength)
            return false;

        return true;
    }

    // 安全密码校验:大写,小写,数字
    bool UserInfo::isSafePassword(std::u16string_view password)
    {
        size_t length = password.length();

        if (length < kSafePasswordLength)
            return false;

        bool has_upper = false;
        bool has_lower = false;
        bool has_digit = false;

        for (size_t i = 0; i < length; ++i) {
            char16_t character = password[i];

            if (std::iswupper(character))
                has_upper = true;

            if (std::iswlower(character))
                has_lower = true;

            if (std::iswdigit(character))
                has_digit = true;
        }

        return has_upper && has_lower && has_digit;
    }


    bool UserInfo::isValid() const
    {
        return !name.empty() && !pwdSalt.empty() && !pwdGroup.empty() && !pwdVerifier.empty();
    }

    //头像大小不能超过64k
    bool UserInfo::isValidHeaderImage() const
    {
        if (0 == headerImage.size() || headerImage.size() >= kMaxHeaderImageSize)
            return false;

        return true;
    }

    // static 从protobuf的数据转换为本地数据结构
    UserInfo UserInfo::parseFrom(const proto::UserInfo& serialized)
    {
        UserInfo userInfo;

        userInfo.code = serialized.code();
        userInfo.name = utf16FromUtf8(serialized.name());
        userInfo.realName = utf16FromUtf8(serialized.real_name());
        userInfo.nickName = utf16FromUtf8(serialized.nick_name());
        userInfo.headerImage = serialized.header_image();               //头像二进制数据
        userInfo.remark = utf16FromUtf8(serialized.remark());
        userInfo.signature = utf16FromUtf8(serialized.signature());
        userInfo.gender = serialized.gender();
        userInfo.birthday = serialized.birthday();
        userInfo.online = serialized.online();

        userInfo.continent = base::CityLocation::parseFrom(serialized.continent());
        userInfo.country = base::CityLocation::parseFrom(serialized.country());
        userInfo.province = base::CityLocation::parseFrom(serialized.province());
        userInfo.city = base::CityLocation::parseFrom(serialized.city());

        userInfo.email = serialized.email();
        userInfo.emailAuth = serialized.email_auth();
        userInfo.mobile = serialized.mobile();

        userInfo.pwdGroup = serialized.group();
        userInfo.pwdSalt = serialized.salt();
        userInfo.pwdVerifier = serialized.verifier();

        return userInfo;
    }

    //从本地数据结构转换为protobuf的消息
    proto::UserInfo UserInfo::serialize() const
    {
        proto::UserInfo userInfo;

        userInfo.set_code(code);
        userInfo.set_name(utf8FromUtf16(name));
        userInfo.set_real_name(utf8FromUtf16(realName));
        userInfo.set_nick_name(utf8FromUtf16(nickName));
        userInfo.set_header_image(headerImage);             //头像二进制数据
        userInfo.set_remark(utf8FromUtf16(remark));
        userInfo.set_signature(utf8FromUtf16(signature));
        userInfo.set_gender(proto::GenderType(gender));     //int 转 enum
        userInfo.set_birthday(birthday);
        userInfo.set_online(online);

        userInfo.mutable_continent()->CopyFrom(continent.serialize());
        userInfo.mutable_country()->CopyFrom(country.serialize());
        userInfo.mutable_province()->CopyFrom(province.serialize());
        userInfo.mutable_city()->CopyFrom(city.serialize());

        userInfo.set_email(email);
        userInfo.set_email_auth(emailAuth);
        userInfo.set_mobile(mobile);

        userInfo.set_group(pwdGroup);
        userInfo.set_salt(pwdSalt);
        userInfo.set_verifier(pwdVerifier);

        //LOG(LS_INFO) << "UserInfo::serialize befor continent: " << continent.name << ", country: " << country.name
        //    << ", province: " << province.name << ", city: " << city.name;

        //LOG(LS_INFO) << "UserInfo::serialize continent: " << userInfo.continent().name() << ", country: " << userInfo.country().name()
        //    << ", province: " << userInfo.province().name() << ", city: " << userInfo.city().name();

        return userInfo;
    }

} // namespace base


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chrisLee_sz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值